diff --git a/src/components/CopyPageButton/index.tsx b/src/components/CopyPageButton/index.tsx new file mode 100644 index 0000000..3d3b916 --- /dev/null +++ b/src/components/CopyPageButton/index.tsx @@ -0,0 +1,233 @@ +import React, { useState, useCallback } from "react"; +import styles from "./styles.module.css"; + +/** + * Converts HTML content to clean markdown-like text. + * Preserves headings, code blocks, lists, and basic formatting. + */ +function htmlToCleanText(element: Element): string { + const lines: string[] = []; + + function processNode(node: Node, depth: number = 0): void { + if (node.nodeType === Node.TEXT_NODE) { + const text = node.textContent?.trim(); + if (text) { + lines.push(text); + } + return; + } + + if (node.nodeType !== Node.ELEMENT_NODE) return; + + const el = node as Element; + const tagName = el.tagName.toLowerCase(); + + // Skip certain elements + if ( + el.classList.contains("theme-doc-toc-desktop") || + el.classList.contains("theme-doc-toc-mobile") || + el.classList.contains("pagination-nav") || + el.classList.contains("theme-doc-breadcrumbs") || + el.classList.contains("theme-doc-version-badge") || + el.classList.contains("theme-doc-version-banner") || + el.classList.contains("copyPageButton") || + tagName === "button" || + tagName === "nav" || + tagName === "script" || + tagName === "style" + ) { + return; + } + + // Handle different elements + switch (tagName) { + case "h1": + lines.push(`\n# ${el.textContent?.trim()}\n`); + break; + case "h2": + lines.push(`\n## ${el.textContent?.trim()}\n`); + break; + case "h3": + lines.push(`\n### ${el.textContent?.trim()}\n`); + break; + case "h4": + lines.push(`\n#### ${el.textContent?.trim()}\n`); + break; + case "h5": + lines.push(`\n##### ${el.textContent?.trim()}\n`); + break; + case "h6": + lines.push(`\n###### ${el.textContent?.trim()}\n`); + break; + case "p": + lines.push(`\n${el.textContent?.trim()}\n`); + break; + case "pre": + // Code block - get the language if available + const codeEl = el.querySelector("code"); + const lang = + codeEl?.className + ?.split(" ") + .find((c) => c.startsWith("language-")) + ?.replace("language-", "") || ""; + const code = codeEl?.textContent?.trim() || el.textContent?.trim(); + lines.push(`\n\`\`\`${lang}\n${code}\n\`\`\`\n`); + break; + case "code": + // Inline code (if not inside pre) + if (el.parentElement?.tagName.toLowerCase() !== "pre") { + lines.push(`\`${el.textContent?.trim()}\``); + } + break; + case "ul": + case "ol": + lines.push(""); + el.childNodes.forEach((child) => processNode(child, depth)); + lines.push(""); + break; + case "li": + const prefix = el.parentElement?.tagName.toLowerCase() === "ol" ? "1." : "-"; + lines.push(`${" ".repeat(depth)}${prefix} ${el.textContent?.trim()}`); + break; + case "blockquote": + const quoteText = el.textContent?.trim(); + if (quoteText) { + lines.push( + `\n> ${quoteText.split("\n").join("\n> ")}\n` + ); + } + break; + case "table": + // Handle tables + const rows = el.querySelectorAll("tr"); + rows.forEach((row, rowIndex) => { + const cells = row.querySelectorAll("th, td"); + const cellTexts = Array.from(cells).map( + (cell) => cell.textContent?.trim() || "" + ); + lines.push(`| ${cellTexts.join(" | ")} |`); + if (rowIndex === 0 && row.querySelector("th")) { + lines.push(`| ${cellTexts.map(() => "---").join(" | ")} |`); + } + }); + lines.push(""); + break; + case "a": + const href = el.getAttribute("href"); + const text = el.textContent?.trim(); + if (href && text && !href.startsWith("#")) { + lines.push(`[${text}](${href})`); + } else if (text) { + lines.push(text); + } + break; + case "strong": + case "b": + lines.push(`**${el.textContent?.trim()}**`); + break; + case "em": + case "i": + lines.push(`*${el.textContent?.trim()}*`); + break; + case "br": + lines.push("\n"); + break; + case "hr": + lines.push("\n---\n"); + break; + case "img": + const alt = el.getAttribute("alt") || ""; + const src = el.getAttribute("src") || ""; + lines.push(`![${alt}](${src})`); + break; + default: + // Process children for container elements + if ( + tagName === "div" || + tagName === "section" || + tagName === "article" || + tagName === "main" || + tagName === "span" + ) { + el.childNodes.forEach((child) => processNode(child, depth)); + } + } + } + + processNode(element, 0); + + // Clean up the result + return lines + .join("") + .replace(/\n{3,}/g, "\n\n") // Remove excessive newlines + .replace(/^\s+|\s+$/g, "") // Trim + .replace(/[ \t]+$/gm, ""); // Remove trailing spaces +} + +interface CopyPageButtonProps { + className?: string; +} + +export default function CopyPageButton({ + className, +}: CopyPageButtonProps): React.ReactElement { + const [copied, setCopied] = useState(false); + + const handleCopy = useCallback(async () => { + // Find the main content area + const content = document.querySelector(".theme-doc-markdown.markdown"); + if (!content) { + console.warn("Could not find markdown content"); + return; + } + + try { + const cleanText = htmlToCleanText(content); + await navigator.clipboard.writeText(cleanText); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } catch (err) { + console.error("Failed to copy:", err); + } + }, []); + + return ( + + ); +} diff --git a/src/components/CopyPageButton/styles.module.css b/src/components/CopyPageButton/styles.module.css new file mode 100644 index 0000000..64e0efd --- /dev/null +++ b/src/components/CopyPageButton/styles.module.css @@ -0,0 +1,64 @@ +.copyButton { + display: inline-flex; + align-items: center; + gap: 0.5rem; + padding: 0.5rem 0.75rem; + font-size: 0.875rem; + font-weight: 500; + font-family: var(--font-sans); + color: var(--ifm-color-primary); + background: transparent; + border: 1px solid var(--ifm-color-primary); + border-radius: var(--radius-md); + cursor: pointer; + transition: all 0.2s ease; + white-space: nowrap; +} + +.copyButton:hover { + color: var(--ifm-color-primary); + border-color: var(--ifm-color-primary); + background: color-mix(in oklab, var(--ifm-color-primary) 8%, transparent); +} + +.copyButton:active { + transform: scale(0.98); +} + +.copyButton.copied { + color: var(--color-success, #00871d); + border-color: var(--color-success, #00871d); + background: color-mix(in oklab, var(--color-success, #00871d) 8%, transparent); +} + +.copyButton svg { + flex-shrink: 0; +} + +.label { + display: inline; +} + +/* Hide label on smaller screens */ +@media (max-width: 768px) { + .label { + display: none; + } + + .copyButton { + padding: 0.5rem; + } +} + +/* Dark mode adjustments */ +[data-theme="dark"] .copyButton { + background: transparent; +} + +[data-theme="dark"] .copyButton:hover { + background: color-mix(in oklab, var(--ifm-color-primary) 15%, transparent); +} + +[data-theme="dark"] .copyButton.copied { + background: color-mix(in oklab, var(--color-success, #00871d) 15%, transparent); +} diff --git a/src/theme/DocItem/Layout/index.tsx b/src/theme/DocItem/Layout/index.tsx new file mode 100644 index 0000000..07e2c99 --- /dev/null +++ b/src/theme/DocItem/Layout/index.tsx @@ -0,0 +1,19 @@ +import React, { type ReactNode } from "react"; +import Layout from "@theme-original/DocItem/Layout"; +import type LayoutType from "@theme/DocItem/Layout"; +import type { WrapperProps } from "@docusaurus/types"; +import CopyPageButton from "@site/src/components/CopyPageButton"; +import styles from "./styles.module.css"; + +type Props = WrapperProps; + +export default function LayoutWrapper(props: Props): ReactNode { + return ( +
+
+ +
+ +
+ ); +} diff --git a/src/theme/DocItem/Layout/styles.module.css b/src/theme/DocItem/Layout/styles.module.css new file mode 100644 index 0000000..d3161d6 --- /dev/null +++ b/src/theme/DocItem/Layout/styles.module.css @@ -0,0 +1,28 @@ +.docItemWrapper { + position: relative; +} + +.copyButtonContainer { + display: flex; + justify-content: flex-end; + margin-bottom: 0.5rem; + padding-right: 0; +} + +/* Adjust position on larger screens */ +@media (min-width: 997px) { + .copyButtonContainer { + position: absolute; + top: 0; + right: -60px; + margin-bottom: 0; + z-index: 10; + } +} + +/* Even wider screens - push further right */ +@media (min-width: 1200px) { + .copyButtonContainer { + right: -100px; + } +} diff --git a/versioned_docs/version-0.12 (stable)/_category_.json b/versioned_docs/version-0.12 (stable)/_category_.json new file mode 100644 index 0000000..38de553 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Docs", + "position": 1, + "collapsed": false +} diff --git a/versioned_docs/version-0.12 (stable)/compiler/_category_.yml b/versioned_docs/version-0.12 (stable)/compiler/_category_.yml new file mode 100644 index 0000000..adabe49 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/compiler/_category_.yml @@ -0,0 +1,4 @@ +label: Compiler +# Determines where this documentation section appears relative to other sections on the main documentation page (which is the parent of this folder in the miden-docs repository) +position: 8 +collapsed: true diff --git a/versioned_docs/version-0.12 (stable)/compiler/appendix/_category_.yml b/versioned_docs/version-0.12 (stable)/compiler/appendix/_category_.yml new file mode 100644 index 0000000..ab3d9c7 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/compiler/appendix/_category_.yml @@ -0,0 +1,4 @@ +label: Compiler +# Determines where this documentation section appears relative to other sections in the parent folder +position: 4 +collapsed: true diff --git a/versioned_docs/version-0.12 (stable)/compiler/appendix/calling-conventions.md b/versioned_docs/version-0.12 (stable)/compiler/appendix/calling-conventions.md new file mode 100644 index 0000000..bee90c0 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/compiler/appendix/calling-conventions.md @@ -0,0 +1,304 @@ +--- +title: Calling Conventions +draft: true +--- + +# Calling conventions + +This document describes the various calling conventions recognized/handled by the compiler, +including a specification for the interaction with the IR type system. + +There are four calling conventions represented in the compiler: + +- `C` aka `SystemV`, which corresponds to the C ABI commonly used for C foreign-function interfaces (FFI). + We specifically use the System V ABI because it is well understood, documented, and straightforward. +- `Fast`, this convention allows the compiler to follow either the `C` calling convention, or modify it + as it sees fit on a function-by-function basis. This convention provides no guarantees about how a + callee will expect arguments to be passed, so should not be used for functions which are expected to + have a stable, predictable interface. This is a good choice for local functions, or functions which are + only used within an executable/library and are not part of the public interface. +- `Kernel`, this is a special calling convention that is used when defining kernel modules in the IR. + Functions which are part of the kernel's public API are required to use this convention, and it is not + possible to call a function via `syscall` if the callee is not defined with this convention. Because of + the semantics of `syscall`, this convention is highly restrictive. In particular, it is not permitted to + pass pointer arguments, or aggregates containing pointers, as `syscall` involves a context switch, and + thus memory in the caller is not accessible to the callee, and vice versa. +- `Contract`, this is a special calling convention that is used when defining smart contract functions, i.e. + functions that can be `call`'d. The compiler will not permit you to `call` a function if the callee is not + defined with this convention, and functions with this convention cannot be called via `exec`. Like `syscall`, + the `call` instruction involves a context switch, however, unlike the `Kernel` convention, the `Contract` + convention is allowed to have types in its signature that are/contain pointers, with certain caveats around + those pointers. + +All four conventions above are based on the System V C ABI, tailored to the Miden VM. The only exception is +`Fast`, which may modify the ABI arbitrarily as it sees fit, and makes no guarantees about what modifications, +if any, it will make. + +## Data representation + +The following is a description of how the IR type system is represented in the `C` calling convention. Later, +a description of how the other conventions extend/restrict/modify this representation will be provided. + +### Scalars + +General type | C Type | IR Type | `sizeof` | Alignment (bytes) | Miden Type +-|-|-|-|-|- + Integer | `_Bool`/`bool` | `I1` | 1 | 1 | u32 + Integer | `char`, `signed char` | `I8` | 1 | 1 | i32[^1] + Integer | `unsigned char` | `U8` | 1 | 1 | u32 + Integer | `short` / `signed short` | `I16` | 2 | 2 | i32[^1] + Integer | `unsigned short` | `U16` | 2 | 2 | u32 + Integer | `int` / `signed int` / `enum` | `I32` | 4 | 4 | i32[^1][^8] + Integer | `unsigned int` | `U32` | 4 | 4 | u32 + Integer | `long` / `signed long` | `I32` | 4 | 4 | i32[^1] + Integer | `unsigned long` / `size_t` | `U32` | 4 | 4 | u32 + Integer | `long long` / `signed long long` | `I64` | 8 | 8 | i64[^2] + Integer | `unsigned long long` | `U64` | 8 | 8 | u64[^3] + Pointer | *`any-type *`* / *`any-type (*)()`* | `Ptr(_)` | 4 | 4 | u32[^6][^7] + Floating point | `float` | `F32` | 4 | 4 | u32[^4] + Floating point | `double` | `F64` | 8 | 8 | u64[^4] + Floating point | `long double` | 16 | 16 | (none)[^5] + +[^1]: i32 is not a native Miden type, but is implemented using compiler intrinsics on top of the native u32 type + +[^2]: i64 is not a native Miden type, but is implemented using compiler intrinsics on top of the stdlib u64 type + +[^3]: u64 is not a native Miden type, but is implemented in software using two 32-bit limbs (i.e. a pair of field elements) + +[^4]: floating-point types are not currently supported, but will be implemented using compiler intrinsics + +[^5]: `long double` values correspond to 128-bit IEEE-754 quad-precision binary128 values. These are not currently +supported, and we have no plans to support them in the near term. Should we ever provide such support, we will do +so using compiler intrinsics. + +[^6]: A null pointer (for all types) always has the value zero. + +[^7]: Miden's linear memory is word-addressable, not byte-addressable. The `Ptr` type has an `AddressSpace` parameter, +that by default is set to the byte-addressable address space. The compiler translates values of `Ptr` type that are in +this address space, into the Miden-native, word-addressable address space during codegen of load/store operations. See +the section on the memory model below for more details. + +[^8]: An `enum` is `i32` if all members of the enumeration can be represented by an `int`/`unsigned int`, otherwise it +uses i64. + +:::note + +The compiler does not support scalars larger than one word (128 bits) at this time. As a result, anything that is +larger than that must be allocated in linear memory, or in an automatic allocation (function-local memory), and passed +around by reference. + +::: + +The native scalar type for the Miden VM is a "field element", specifically a 64-bit value representing an integer +in the "Goldilocks" field, i.e. `0..(2^64-2^32+1)`. A number of instructions in the VM operate on field elements directly. +However, the native integral/pointer type, i.e. a "machine word", is actually `u32`. This is because a field element +can fully represent 32-bit integers, but not the full 64-bit integer range. Values of `u32` type are valid field element +values, and can be used anywhere that a field element is expected (barring other constraints). + +Miden also has the notion of a "word", not to be confused with a "machine word" (by which we mean the native integral +type used to represent pointers), which corresponds to a set of 4 field elements. Words are commonly used in Miden, +particularly to represent hashes, and a number of VM instructions operate on word-sized operands. As an aside, 128-bit +integer values are represented using a word, or two 64-bit limbs (each limb consisting of two 32-bit limbs). + +All integral types mentioned above, barring field elements, use two's complement encoding. Unsigned integral types +make use of the sign bit to change the value range (i.e. 0..2^32-1, rather than -2^31..2^31-1), but the encoding follows +two's complement rules. + +The Miden VM only has native support for field elements, words, and `u32`; all other types are implemented in software +using intrinsics. + +### Aggregates and unions + +Structures and unions assume the alignment of their most strictly aligned component. Each member is assigned to the +lowest available offset with the appropriate alignment. The size of any object is always a multiple of the object's alignment. +An array uses the same alignment as its elements. Structure and union objects can require padding to meet size and alignment +constraints. The contents of any padding is undefined. + +### Memory model + +Interacting with memory in Miden is quite similar to WebAssembly in some ways: + +* The address space is linear, with addresses starting at zero, and ranging up to 2^32-1 +* There is no memory protection per se, you either have full read/write access, or no access to a specific memory context +* How memory is used is completely up to the program being executed + +This is where it begins to differ though, and takes on qualities unique to Miden (in part, or whole): + +* Certain regions of the address space are "reserved" for special uses, improper use of those regions may result in +undefined behavior. +* Miden has different types of function call instructions: `call` vs `syscall` vs `exec`. The first two +perform a context switch when transferring control to the callee, and the callee has no access to the +caller's memory (and the caller has no access to the callee's memory). As a result, references to memory +cannot be passed from caller to callee in arguments, nor can they be returned from the callee to the caller. +* Most significant of all though, is that Miden does not have byte-addressable memory, it is instead word-addressable, +i.e. every address refers to a full word. +* It is not possible to load a specific field element from a word in memory, unless it happens to be the first element +of the word. Instead, one must load the full word, and drop the elements you don't need. + +This presents some complications, particularly: + +* Most languages assume a byte-oriented memory model, which is not trivially mapped to a word-oriented model +* Simple things, such as taking the address of a field in a struct, and then dereferencing it, cannot be directly +represented in Miden using native pointer arithmetic and `load` instruction. Operations like this must be translated +into instruction sequences that load whole words from memory, extract the data needed, and discard the unused bits. +This makes the choice of where in memory to store something much more important than byte-addressable memory, as +loads of values which are not aligned to element or word boundaries can be quite inefficient in some cases. + +The compiler solves this by providing a byte-addressable IR, and internally translating operations in the IR to the equivalent +sequence of Miden instructions needed to emulate that operation. This translation is done during code generation, and uses +the following semantics to determine how a particular operation gets lowered: + +* A byte-addressable pointer can be emulated in Miden's word-addressable environment using three pieces of information: + - The address of the word containing the first byte of the value, this is a "native" Miden address value + - The index of the field element within that word containing the first byte of the value + - The offset (in bytes) from the start of the 4 byte chunk represented by the selected element, corresponding + to the first byte of the value. Since the chunk is represented as a u32 value, the offset is relative to the + most-significant bit (i.e. the byte with the lowest address is found in bits 55-63, since Miden integers are little-endian) +* This relies on us treating Miden's linear memory as an array of 16-byte chunks of raw memory (each word is 4 field elements, +each element represents a 4-byte chunk). In short, much like translating a virtual memory address to a physical one, we must +translate byte-addressable "virtual" pointers to "real" Miden pointers with enough metadata to be able to extract the data we're +trying to load (or encode the data we're trying to store). + +Because we're essentially emulating byte-addressable memory on word-addressable memory, loads/stores can range from simple and +straightforward, to expensive and complicated, depending on the size and alignment of the value type. The process goes as follows: + +* If the value type is word-aligned, it can be loaded/stored in as little as a single instruction depending on the size of the type +* Likewise if the value type is element-aligned, and the address is word-aligned +* Element-aligned values require some extra instructions to load a full word and drop the unused elements (or in the case of stores, +loading the full word and replacing the element being stored) +* Loads/stores of types with sub-element alignment depend on the alignment of the pointer itself. Element or word-aligned addresses +are still quite efficient to load/store from, but if the first byte of the value occurs in the middle of an element, then the bytes +of that value must be shifted into place (or unused bytes masked out). If the value crosses an element boundary, then the bytes in +both elements must be isolated and shifted into position such that they can be bitwise-OR'd together to obtain the aligned value on +the operand stack. If a value crosses a word boundary, then elements from both words must be loaded, irrelevant ones discarded, the +relevant bytes isolated and shifted into position so that the resulting operand on the stack is aligned and laid out correctly. +* Stores are further complicated by the need to preserve memory that is not being explicitly written to, so values that do not overwrite +a full word or element, require combining bytes from the operand being stored and what currently resides in memory. + +The worst case scenario for an unaligned load or store involves a word-sized type starting somewhere in the last element of the first +word. This will require loading elements from three consecutive words, plus a lot of shuffling bits around to get the final, aligned +word-sized value on the operand stack. Luckily, such operations should be quite rare, as by default all word-sized scalar types are +word-aligned or element-aligned, so an unaligned load or store would require either a packed struct, or a type such as an array of +bytes starting at some arbitrary address. In practice, most loads/stores are likely to be element-aligned, so most overhead from +emulation will come from values which cross an element or word boundary. + +## Function calls + +This section describes the conventions followed when executing a function call via `exec`, including how arguments are passed on the +operand stack, stack frames, etc. Later, we'll cover the differences when executing calls via `call` or `syscall`. + +### Locals and the stack frame + +Miden does not have registers in the style of hardware architectures. Instead it has an operand stack, on which an arbitrary number of +operands may be stored, and local variables. In both cases - an operand on the operand stack, or a single local variable - the value +type is nominally a field element, but it is easier to reason about them as untyped element-sized values. The operand stack is used +for function arguments, return values, temporary variables, and scratch space. Local variables are not always used, but are typically +used to hold multiply-used values which you don't want to keep on the operand stack, function-scoped automatic allocations (i.e. `alloca`), +and other such uses. + +Miden does not have a stack frame per se. When you call a procedure in Miden Assembly, any local variables declared by that procedure +are allocated space in a reserved region of linear memory in a single consecutive chunk. However, there is no stack or frame pointer, +and because Miden is a Harvard architecture machine, there are no return addresses. Instead, languages (such as C) which have the concept +of a stack frame with implications for the semantics of say, taking the address of a local variable, will need to emit code in function +prologues and epilogues to maintain a shadow stack in Miden's linear memory. If all you need is local variables, you can get away with +leaning on Miden's notion of local variables without implementing a shadow stack. + +Because there are no registers, the notion of callee-saved or caller-saved registers does not have a direct equivalent in Miden. However, +in its place, a somewhat equivalent set of rules defines the contract between caller and callee in terms of the state of the operand stack, +those are described below in the section covering the operand stack. + +#### The shadow stack + +Miden is a [Harvard](https://en.wikipedia.org/wiki/Harvard_architecture) architecture; as such, code and data are not in the same memory +space. More precisely, in Miden, code is only addressable via the hash of the MAST root of that code, which must correspond to code that +has been loaded into the VM. The hash of the MAST root of a function can be used to call that function both directly and indirectly, but +that is the only action you can take with it. Code can not be generated and called on the fly, and it is not stored anywhere that is +accessible to code that is currently executing. + +One consequence of this is that there are no return addresses or instruction pointers visible to executing code. The runtime call stack is +managed by the VM itself, and is not exposed to executing code in any way. This means that address-taken local C variables need to be on a +separate stack in linear memory (which we refer to as a "shadow stack"). Not all functions necessarily require a frame in the shadow stack, +as it cannot be used to perform unwinding, so only functions which have locals require a frame. + +The Miden VM actually provides some built-in support for stack frames when using Miden Assembly. Procedures which are declared with some +number of locals, will be automatically allocated sufficient space for those locals in a reserved region of linear memory when called. If +you use the `locaddr` instruction to get the actual address of a local, that address can be passed as an argument to callees (within the +constraints of the callee's calling convention). + +Languages with more elaborate requirements with regard to the stack will need to implement their own shadow stack, and emit code in function +prologues/epilogues to manage it. + +#### The operand stack + +The Miden virtual machine is a stack machine, not a register machine. Rather than having a fixed set of registers that are used to +store and manipulate scalar values, the Miden VM has the operand stack, which can hold an arbitrary number of operands (where each +operand is a single field element), of which the first 16 can be directly manipulated using special stack instructions. The operand +stack is, as the name implies, a last-in/first-out data structure. + +The following are basic rules all conventions are expected to follow with regard to the operand stack: + +1. The state of the operand stack from the point of view of the caller should be preserved, with two exceptions: + - The callee is expected to consume all of its arguments, and the caller will expect those operands to be gone when control is returned to it + - If the callee signature declares a return value, the caller expects to see that on top of the stack when control is returned to it +2. No more than 16 elements of the operand stack may be used for passing arguments. If more than that is required to represent all of the arguments, +then one of the following must happen: + - Spill to stack frame: in this scenario, up to 15 elements of the operand stack are used for arguments, and the remaining element is used to hold + a pointer to a local variable in the caller's stack frame. That local variable is a struct whose fields are the spilled arguments, appearing in + the same order as they would be passed. The callee must use the pointer it is given to compute the effective address for each spilled argument + that it wishes to access. + - Spill to heap: this is basically identical to the approach above, except the memory is allocated from the global heap, rather than using memory + associated with the caller's stack frame. + - Spill to the advice provider: in this scenario, 12 elements of the stack are used for arguments, and the remaining 4 are used to hold a hash + which refers to the remaining arguments on the advice provider stack. The callee must arrange to fetch the spilled arguments from the advice + provider using that hash. + +#### Function signatures + +Miden Abstract Syntax Trees (MASTs) do not have any notion of functions, and as such are not aware of parameters, return values, etc. For +this document, that's not a useful level of abstraction to examine. Even a step higher, Miden Assembly (MASM) has functions (procedures +in MASM parlance), but no function signature, i.e. given a MASM procedure, there is no way to know how many arguments it expects, how +many values it returns, let alone the types of arguments/return values. Instead, we're going to specify calling conventions in terms of +Miden IR, which has a fairly expressive type system more or less equivalent to that of LLVM, and how that translates to Miden primitives. + +Functions in Miden IR always have a signature, which specify the following: + +* The calling convention required to call the function +* The number and types of the function arguments +* The type of value, if any, returned by the function, and whether it is returned by value or reference + +The following table relates IR types to how they are expected to be passed from the caller to the callee, and vice versa: + +Type | Parameter | Result | +--------------------------|---------------|----------| +scalar | direct | direct | +empty struct or union[^1] | ignored | ignored | +scalar struct or union[^2] | direct | direct | +other struct or union | indirect | indirect | +array | indirect | N/A | + +[^1]: Zero-sized types have no representation in memory, so they are ignored/skipped + +[^2]: Any struct or union that recursively (including through nested structs, +unions, and arrays) contains just a single scalar value and is not specified to +have greater than natural alignment. + +The compiler will automatically generate code that follows these rules, but if emitting MASM from your own backend, it is necessary to do so manually. +For example, a function whose signature specifies that it returns a non-scalar struct by value, must actually be written such that it expects to receive +a pointer to memory allocated by the caller sufficient to hold the return value, as the first parameter of the function (i.e. the parameter is prepended +to the parameter list). When returning, the function must write the return value to that pointer, rather than returning it on the operand stack. In this +example, the return value is returned indirectly (by reference). + +A universal rule is that the arguments are passed in reverse order, i.e. the first argument in the parameter list of a function will be on top of the +operand stack. This is different than many Miden instructions which seemingly use the opposite convention, e.g. `add`, which expects the right-hand +operand on top of the stack, so `a + b` is represented like `push a, push b, add`. If we were to implement `add` as a function, it would instead be +`push b, push a, exec.add`. The rationale behind this is that, in general, the more frequently used arguments appear earlier in the parameter list, +and thus we want those closer to the top of the operand stack to reduce the amount of stack manipulation we need to do. + +Arguments/return values are laid out on the operand stack just like they would be as if you had just loaded it from memory, so all arguments are aligned, +but may span multiple operands on the operand stack as necessary based on the size of the type (i.e. a struct type that contains a `u32` and a `i1` +field would require two operands to represent). If the maximum number of operands allowed for the call is reached, any remaining arguments must be +spilled to the caller's stack frame, or to the advice provider. The former is used in the case of `exec`/`dynexec`, while the latter is used for `call` +and `syscall`, as caller memory is not accessible to the callee with those instructions. + +While ostensibly 16 elements is the maximum number of operands on the operand stack that can represent function arguments, due to the way `dynexec`/`dyncall` +work, it is actually limited to 12 elements, because at least 4 must be free to hold the hash of the function being indirectly called. diff --git a/versioned_docs/version-0.12 (stable)/compiler/appendix/canonabi-adhocabi-mismatch.md b/versioned_docs/version-0.12 (stable)/compiler/appendix/canonabi-adhocabi-mismatch.md new file mode 100644 index 0000000..a83132f --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/compiler/appendix/canonabi-adhocabi-mismatch.md @@ -0,0 +1,315 @@ +--- +title: Canonical ABI vs Miden ABI Incompatibility +draft: true +--- + +# Canonical ABI vs Miden ABI incompatibility + +:::note + +This document describes an issue that arises when trying to map the ad-hoc calling convention/ABI +used by various Miden Assembly procedures, such as those comprising the transaction kernel, and +the "canonical" ABI(s) representable in Rust. It proposes a solution to this problem in the form +of _adapter functions_, where the details of a given adapter are one of a closed set of known +ABI _transformation strategies_. + +::: + +## Summary + +The gist of the problem is that in Miden, the size and number of procedure results is only constrained +by the maximum addressable operand stack depth. In most programming languages, particularly those in +which interop is typically performed using some variant of the C ABI (commonly the one described +in the System V specification), the number of results is almost always limited to a single result, +and the size of the result type is almost always limited to the size of a single machine word, in +some cases two. On these platforms, procedure results of greater arity or size are typically handled +by reserving space in the caller's stack frame, and implicitly prepending the parameter list of the +callee with an extra parameter: a pointer to the memory allocated for the return value. The callee +will directly write the return value via this pointer, instead of returning a value in a register. + +In the case of Rust, this means that attempting to represent a procedure that returns multiple values, +or returns a larger-than-machine-word type, such as `Word`, will trigger the implicit transformation +described above, as this is allowed by the standard Rust calling conventions. Since various Miden +procedures that are part of the standard library and the transaction kernel are affected by this, +the question becomes "how do we define bindings for these procedures in Rust?". + +The solution is to have the compiler emit glue code that closes the gap between the two ABIs. It +does so by generating adapter functions, which wrap functions that have an ABI unrepresentable in +Rust, and orchestrate lifting/lowering arguments and results between the adapter and the "real" +function. + +When type signatures are available for all Miden Assembly procedures, we can completely automate +this process. For now, we will require a manually curated list of known procedures, their signatures, +and the strategy used to "adapt" those procedures for binding in Rust. + +## Background + +After analyzing all of the functions in the transaction kernel API, the most common cause of a mismatch +between Miden and Rust ABIs, is due to implicit "sret" parameters, i.e. the transformation mentioned +above which inserts an implicit pointer to the caller's stack frame for the callee to write the return +value to, rather than doing so in a register (or in our case, on the operand stack). This seems to +happen for any type that is larger than 8 bytes (i64). + +:::tip + +For a complete list of the transaction kernel functions, in WIT format, see +[miden.wit](https://github.com/0xMiden/compiler/blob/main/tests/rust-apps-wasm/wit-sdk/sdk/wit/miden.wit). + +::: + +For most transaction kernel functions, the adapter function can be generated automatically using the +pattern recognition and adapter functions described below. + +### Prerequisites + +- The compiler must know the type signature for any function we wish to apply the adapter strategy to + +### Implementation + +The compiler will analyze every component import to determine if that import requires an adapter, +as determined by matching against a predefined set of patterns. The adapter generation will take +place in the frontend, as it has access to all of the needed information, and ensures that we do +not have any transformations or analyses that make decisions on the un-adapted procedure. + +The following pseudo-code can be used to recognize the various Miden ABI patterns: + +```rust +pub enum MidenAbiPattern { + /// Calling this procedure will require an sret parameter on the Rust side, so + /// we need to emit an adapter that will lift/lower calls according to that + /// strategy. + ReturnViaPointer, + /// The underlying procedure is fully representable in Rust, and requires no adaptation. + NoAdapterNeeded, +} + +pub struct MidenAbiPatternRecognition { + pattern: Option, + component_function: ComponentFunctionType, + wasm_core_func: Signature, + tx_kernel_function: Signature, +} + +pub fn recognize_miden_abi_pattern( + component_function: &ComponentFunctionType, + wasm_core_func: &Signature, + tx_kernel_func: &Signature) -> MidenAbiPatternRecognition { + if wasm_core_func == tx_kernel_func { + return MidenAbiPatternRecognition { + pattern: Some(NoAdapterNeeded), + component_function, + wasm_core_function, + tx_kernel_function, + }; + } else if component_function.returns[0].byte_size > 8 && wasm_core_func.params.last() == I32 { + return MidenAbiPatternRecognition { + pattern: Some(ReturnViaPointer), + component_function, + wasm_core_function, + tx_kernel_function, + }; + } else { + return MidenAbiPatternRecognition { + pattern: None, + component_function, + wasm_core_function, + tx_kernel_function, + }; + } +} +``` + +The following pseudo-code can then be used to generate the adapter function: + +```rust +pub fn generate_adapter(recognition: MidenAbiPatternRecognition) { + match recognition.pattern { + Some(pattern) => generate_adapter( + pattern, + recognition.component_function, + recognition.wasm_core_function, + recognition.tx_kernel_function + ), + None => use_manual_adapter( + recognition.component_function, + recognition.wasm_core_function, + recognition.tx_kernel_function + ), + } +} + +/// Escape hatch for the cases when the compiler can't generate an adapter function automatically +/// and we need to provide the adapter function manually. +pub fn use_manual_adapter(...) { + // Find and use the manual adapter in the adapter library for the tx_kernel_function +} +``` + +The manual adapter library is a collection of adapter functions that are used when the compiler +can't generate an adapter function automatically so its expected to be provided. The manual adapter +library is a part of the Miden compiler. It is not anticipated that we will have many, or any, of +these; however in the near term we are going to manually map procedures to their adapter strategies, +as we have not yet automated the pattern recognition step. + +### Return-via-pointer adapter + +The return value is expected to be returned by storing its flattened representation in a pointer +passed as an argument. + +Recognize this Miden ABI pattern by looking at the Wasm component function type. If the return value +is bigger than 64 bits, expect the last argument in the Wasm core(HIR) signature to be `i32` (a pointer). + +The adapter function calls the tx kernel function and stores the result in the provided pointer (the +last argument of the Wasm core function). + +Here is the pseudo-code for generating the adapter function for the return-via-pointer Miden ABI +pattern: + +```rust +let ptr = wasm_core_function.params.last(); +let adapter_function = FunctionBuilder::new(wasm_core_function.clone()); +let tx_kernel_function_params = wasm_core_function.params.drop_last(); +let tx_kernel_func_val = adapter_function.call(tx_kernel_function, tx_kernel_function_params); +adapter_function.store(tx_kernel_func_val, ptr); +adapter_function.build(); +``` + +Here is how the adapter might look like in a pseudo-code for the `add_asset` function: + +```wat +/// Takes an Asset as an argument and returns a new Asset +func wasm_core_add_asset(v0: f64, v1: f64, v2: f64, v3: f64, ptr: i32) { + v4 = call tx_kernel_add_asset(v0, v1, v2, v3); + // v4 is a tuple of 4 f64 values + store v4 in ptr; +} +``` + +### No-op adapter + +No adapter is needed. The Wasm core function type is the same as the tx kernel ad-hoc signature. + +This Miden ABI pattern is selected if no other Miden ABI pattern is applicable and the wasm core function signature is the same as the tx kernel ad-hoc signature. + +For example, the `get_id` function falls under this Miden ABI pattern and its calls will be translated to the tx kernel function calls without any modifications. + +## Transaction kernel functions that require manual adapter functions + +### `get_assets` + +`get_assets:func() -> list` in the `note` interface is the only function that requires attention. +In Canonical ABI, any function that returns a dynamic list of items needs to allocate memory in the caller's +module due to the shared-nothing nature of the Wasm component model. For this case, a `realloc` function +is passed as a part of lift/lower Canonical ABI options for the caller to allocate memory in the caller's +module. + +Here are the signatures of the `get_assets` function in the WIT, core Wasm, and the tx kernel ad-hoc ABI: +Comment from the `miden-base` + +```text +#! Writes the assets of the currently executing note into memory starting at the specified address. +#! +#! Inputs: [dest_ptr] +#! Outputs: [num_assets, dest_ptr] +#! +#! - dest_ptr is the memory address to write the assets. +#! - num_assets is the number of assets in the currently executing note. +``` + +Wasm component function type: +`get-assets: func() -> list;` + +Wasm core signature: +`wasm_core_get_assets(i32) -> ()` + +If we add a new `get_assets_count: func() -> u32;` function to the tx kernel and add the assets count +parameter to the `get_assets` function (`get_assets: func(assets_count: u32) -> list;`) +we should have everything we need to manually write the adapter function for the `get_assets` +function. + +The list is expected to be returned by storing the pointer to its first item in a `ptr` pointer +passed as an argument and item count at `ptr + 4 bytes` address (`ptr` points to two pointers). + +We could try to recognize this Miden ABI pattern by looking at the Wasm component function type. If +the return value is a list, expect the last argument in the Wasm core(HIR) signature to be `i32` +(a pointer). The problem is recognizing the list count parameter in the Wasm core(HIR) signature. + +The adapter function calls allocates `asset_count * item_size` memory via the `realloc` call and +passes the pointer to the newly allocated memory to the tx kernel function. + +Here is how the adapter function might look like in a pseudo-code for the `get_assets` function: + +```rust +func wasm_core_get_assets(asset_count: u32, ptr_ptr: i32) { + mem_size = asset_count * item_size; + ptr = realloc(mem_size); + (actual_asset_count, ptr) = call tx_kernel_get_assets(ptr); + assert(actual_asset_count == asset_count); + store ptr in ptr_ptr; + store account_count in ptr_ptr + 4; +} +``` + +:::note + +Since the `get_assets` tx kernel function in the current form can trash the provided memory if +the actual assets count differs from the returned by `get_assets_count`, we can introduce the +asset count parameter to the `get_assets` tx kernel function and check that it the same as the +actual assets count written to memory. + +::: + +## The example of some functions signatures + +### `add_asset` (return-via-pointer Miden ABI pattern) + +Comment from the `miden-base` + +```text +#! Add the specified asset to the vault. +#! +#! Panics: +#! - If the asset is not valid. +#! - If the total value of two fungible assets is greater than or equal to 2^63. +#! - If the vault already contains the same non-fungible asset. +#! +#! Stack: [ASSET] +#! Output: [ASSET'] +#! +#! - ASSET' final asset in the account vault is defined as follows: +#! - If ASSET is a non-fungible asset, then ASSET' is the same as ASSET. +#! - If ASSET is a fungible asset, then ASSET' is the total fungible asset in the account vault +#! after ASSET was added to it. +``` + +Wasm component function type: +`add-asset(core-asset) -> core-asset` + +Wasm core signature: +`wasm_core_add_asset(f64, f64, f64, f64, i32) -> ()` +The last `i32` is a pointer to a returned value (`word`) + +Tx kernel ad-hoc signature: +`tx_kernel_add_asset(felt, felt, felt, felt) -> (felt, felt, felt, felt)` + +### `get_id` (no-adapter-needed Miden ABI pattern) + +Comment from the `miden-base` + +```text +#! Returns the account id. +#! +#! Stack: [] +#! Output: [acct_id] +#! +#! - acct_id is the account id. +``` + +Wasm component function type: +`get-id() -> account-id` + +Wasm core signature: +`wasm_core_get_id() -> f64` + +Tx kernel ad-hoc signature: +`tx_kernel_get_id() -> felt` diff --git a/versioned_docs/version-0.12 (stable)/compiler/appendix/index.md b/versioned_docs/version-0.12 (stable)/compiler/appendix/index.md new file mode 100644 index 0000000..e280831 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/compiler/appendix/index.md @@ -0,0 +1,12 @@ +--- +title: Appendices +draft: true +--- + +# Appendices + +This section contains supplementary material providing deeper technical insights into the Miden compiler and related systems: + +- [Known Limitations](./known-limitations.md) - Details current constraints and unimplemented features. +- [Calling Conventions](./calling-conventions.md) - Explains function call mechanics and argument passing. +- [Canonical ABI vs Miden ABI](./canonabi-adhocabi-mismatch.md) - Analyzes cross-ABI interoperability challenges. diff --git a/versioned_docs/version-0.12 (stable)/compiler/appendix/known-limitations.md b/versioned_docs/version-0.12 (stable)/compiler/appendix/known-limitations.md new file mode 100644 index 0000000..215633b --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/compiler/appendix/known-limitations.md @@ -0,0 +1,259 @@ +--- +title: Known Limitations +draft: true +--- + +# Known limitations + +:::tip + +See the [issue tracker](https://github.com/0xMiden/compiler/issues) for information +on known bugs. This document focuses on missing/incomplete features, rather than bugs. + +::: + +The compiler is still in its early stages of development, so there are various features that are +unimplemented, or only partially implemented, and the test suite is still limited in scope, so +we are still finding bugs on a regular basis. We are rapidly improving this situation, but it is +important to be aware of this when using the compiler. + +The features discussed below are broken up into sections, to make them easier to navigate and +reference. + +## Rust language support + +### Floating point types + +- Status: **Unsupported** +- Tracking Issue: N/A +- Release Milestone: N/A + +In order to represent `Felt` "natively" in Rust, we were forced to piggy-back on the `f32` type, +which is propagated through to WebAssembly, and allows us to handle those values specially. + +As a result, floating-point types in Rust are not supported at all. Any attempt to use them will +result in a compilation error. We considered this a fair design tradeoff, as floating point math +is unused/rare in the context in which Miden is used, in comparison to fixed-point or field +arithmetic. In addition, implementing floating-point operations in software on the Miden VM would +be extraordinarily expensive, which generally works against the purpose for using floats in the +first place. + +At this point in time, we have no plans to support floats, but this may change if we are able to +find a better/more natural representation for `Felt` in WebAssembly. + +### Function call indirection + +- Status: **Unimplemented** +- Tracking Issue: [#32](https://github.com/0xMiden/compiler/issues/32) +- Release Milestone: [Beta 1](https://github.com/0xMiden/compiler/milestone/4) + +This feature corresponds to `call_indirect` in WebAssembly, and is associated with Rust features +such as trait objects (which use indirection to call trait methods), and closures. Note that the +Rust compiler is able to erase the indirection associated with certain abstractions statically +in some cases, shown below. If Rust is unable to statically resolve all call targets, then `midenc` +will raise an error when it encounters any use of `call_indirect`. + +:::warning + +The following examples rely on `rustc`/LLVM inlining enough code to be able to convert indirect +calls to direct calls. This may require you to enable link-time optimization with `lto = "fat"` +and compile all of the code in the crate together with `codegen-units = 1`, in order to maximize +the amount of inlining that can occur. Even then, it may not be possible to remove some forms of +indirection, in which case you will need to find another workaround. + +::: + +#### Iterator lowered to loop + +```rust +pub fn is_zeroed(bytes: &[u8; 32]) -> bool { + // Rust is able to convert this to a loop, erasing the closure completely + bytes.iter().copied().all(|b| b == 0) +} +``` + +#### Monomorphization + inlining + +```rust +pub fn call(fun: F) -> T +where + F: Fn() -> T, +{ + fun() +} + +#[inline(never)] +pub fn foo() -> bool { true } + +fn main() { + // Rust is able to inline the body of `call` after monomorphization, which results in + // the call to `foo` being resolved statically. + call(foo) +} +``` + +#### Inlined trait impl + +```rust +pub trait Foo { + fn is_foo(&self) -> bool; +} + +impl Foo for u32 { + #[inline(never)] + fn is_foo(&self) -> bool { true } +} + +fn has_foo(items: &[dyn Foo]) -> bool { + items.iter().any(|item| item.is_foo()) +} + +fn main() -> u32 { + // Rust inlines `has_foo`, converts the iterator chain to a loop, and is able to realize + // that the `dyn Foo` items are actually `u32`, and resolves the call to `is_foo` to + // `::is_foo`. + let foo: &dyn Foo = &u32::MAX as &dyn Foo; + has_foo(&[foo]) as u32 +} +``` + +### Miden SDK + +- Status: **Incomplete** +- Tracking Issue: [#159](https://github.com/0xMiden/compiler/issues/159) and [#158](https://github.com/0xMiden/compiler/issues/158) +- Release Milestone: [Beta 1](https://github.com/0xMiden/compiler/milestone/4) + +The Miden SDK for Rust, is a Rust crate that provides the implementation of native Miden types, as +well as bindings to the Miden standard library and transaction kernel APIs. + +Currently, only a very limited subset of the API surface has had bindings implemented. This means +that there is a fair amount of native Miden functionality that is not yet available from Rust. We +will be expanding the SDK rapidly over the next few weeks and months, but for the time being, if +you encounter a missing API that you need, let us know, so we can ensure it is prioritized above +APIs which are lesser used. + +### Rust/Miden FFI (foreign function interface) and interop + +- Status: **Internal Use Only** +- Tracking Issue: [#304](https://github.com/0xMiden/compiler/issues/304) +- Release Milestone: TBD + +While the compiler has functionality to link against native Miden Assembly libraries, binding +against procedures exported from those libraries from Rust can require glue code to be emitted +by the compiler in some cases, and the set of procedures for which this is done is currently +restricted to a hardcoded whitelist of known Miden procedures. + +This affects any procedure which returns a type larger than `u32` (excluding `Felt`, which for +this purpose has the same size). For example, returing a Miden `Word` from a procedure, a common +return type, is not compatible with Rust's ABI - it will attempt to generate code which allocates +stack space in the caller, which it expects the callee to write to, inserting a new parameter at +the start of the parameter list, and expecting nothing to be returned by value. The compiler handles +situations like these using a set of ABI "transformation strategies", which lift/lower differences +between the Rust and Miden ABIs at call boundaries. + +To expose the FFI machinery for use with any Miden procedure, we need type signatures for those +procedures at a minimum, and in some cases we may require details of the calling convention/ABI. +This metadata does not currently exist, but is on the roadmap for inclusion into Miden Assembly +and Miden packaging. Once present, we can open up the FFI for general use. + +## Core Miden functionality + +### Dynamic procedure invocation + +- Status: **Unimplemented** +- Tracking Issue: [#32](https://github.com/0xMiden/compiler/issues/32) +- Release Milestone: [Beta 1](https://github.com/0xMiden/compiler/milestone/4) + +This is a dependency of [Function Call Indirection](#function-call-indirection) described above, +and is the mechanism by which we can perform indirect calls in Miden. In order to implement support +for indirect calls in the Wasm frontend, we need underlying support for `dynexec`, which is not yet +implemented. + +This feature adds support for lowering indirect calls to `dynexec` or `dyncall` instructions, +depending on the ABI of the callee. `dyncall` has an additional dependency on support for +[Cross-Context Procedure Invocation](#cross-context-procedure-invocation). + +A known issue with this feature is that `dyn(exec|call)` consumes a word on the operand stack +for the hash of the callee being invoked, but this word _remains_ on the stack when entering the +callee, which has the effect of requiring procedures to have a different ABI depending on whether +they expect to be dynamically-invoked or not. + +Our solution to that issue is to generate stubs which are used as the target of `dyn(exec|call)`, +the body of which drop the callee hash, fix up the operand stack as necessary, and then uses a +simple `exec` or `call` to invoke the "real" callee. We will emit a single stub for every function +which has its "address" taken, and use the hash of the stub in place of the actual callee hash. + +### Cross-context procedure invocation + +- Status: **Unimplemented** +- Tracking Issue: [#303](https://github.com/0xMiden/compiler/issues/303) +- Release Milestone: [Beta 2](https://github.com/0xMiden/compiler/milestone/5) + +This is required in order to support representing Miden accounts and note scripts in Rust, and +compilation to Miden Assembly. + +Currently, you can write code in Rust that is very close to how accounts and note scripts will +look like in the language, but it is not possible to actually implement either of those in Rust +today. The reasons for this are covered in depth in the tracking issue linked above, but to +briefly summarize, the primary issue has to do with the fact that Rust programs are compiled +for a "shared-everything" environment, i.e. you can pass references to memory from caller to +callee, write to caller memory from the callee, etc. In Miden however, contexts are "shared-nothing" +units of isolation, and thus cross-context operations, such as performing a `call` from a note script +to a method on an account, are not compatible with the usual calling conventions used by Rust and +LLVM. + +The solution to this relies on compiling the Rust code for the `wasm32-wasip2` target, which emits +a new kind of WebAssembly module, known as a _component_. These components adhere to the rules of +the [WebAssembly Component Model](https://component-model.bytecodealliance.org/). Of primary +interest to us, is the fact that components in this model are "shared-nothing", and the ABI used to +communicate across component boundaries, is specially designed to enforce shared-nothing semantics +on caller and callee. In addition to compiling for a specific Wasm target, we also rely on some +additional tooling for describing component interfaces, types, and to generate Rust bindings for +those descriptions, to ensure that calls across the boundary remain opaque, even to the linker, +which ensures that the assumptions of the caller and callee with regard to what address space they +operate in are preserved (i.e. a callee can never be inlined into the caller, and thus end up +executing in the caller's context rather than the expected callee context). + +This is one of our top priorities, as it is critical to being able to use Rust to compile code for +the Miden rollup, but it is also the most complex feature on our roadmap, hence why it is scheduled +for our Beta 2 milestone, rather than Beta 1 (the next release), as it depends on multiple other +subfeatures being implemented first. + +## Packaging + +### Package format + +- Status: **Experimental** +- Tracking Issue: [#121](https://github.com/0xMiden/compiler/issues/121) +- Release Milestone: [Beta 1](https://github.com/0xMiden/compiler/milestone/4) + +This feature represents the ability to compile and distribute a single artifact that contains +the compiled MAST, and all required and optional metadata to make linking against, and executing +packages as convenient as a dynamic library or executable. + +The compiler currently produces, by default, an experimental implementation of a package format +that meets the minimum requirements to support libraries and programs compiled from Rust: + +- Name and semantic version information +- Content digest +- The compiled MAST and metadata about the procedures exported from it +- Read-only data segments and their hashes (if needed by the program, used to load data into the + advice provider when a program is loaded, and to write those segments into linear memory when the + program starts) +- Dependency information (optional, specifies what libraries were linked against during compilation) +- Debug information (optional) + +However, this package format is not yet understood by the Miden VM itself. This means you cannot, +currently, compile a package and then run it using `miden run` directly. Instead, you can use +`midenc run` to load and run code from a package, as the compiler ships with the VM embedded for +use with the interactive debugger, and provides native support for packaging on top of it. You can +also use `midenc debug` to execute your program interactively in the debugger, depending on your +needs. See [Debugging Programs](../guides/debugger.md) for more information on how to use the +debugger, and `midenc help run` for more information on executing programs with the `midenc run` +command. + +While it is possible to emit raw MAST from `midenc`, rather than the experimental package format, +the resulting artifact cannot be run without some fragile and error-prone manual setup, in order +to ensure that the advice provider is correctly initialized with any read-only data segments. For +now, it is recommended that you use the `midenc` tooling for testing programs, until the format +is stabilized. diff --git a/versioned_docs/version-0.12 (stable)/compiler/guides/_category_.yml b/versioned_docs/version-0.12 (stable)/compiler/guides/_category_.yml new file mode 100644 index 0000000..9375afa --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/compiler/guides/_category_.yml @@ -0,0 +1,4 @@ +label: Guides +# Determines where this documentation section appears relative to other sections in the parent folder +position: 3 +collapsed: true diff --git a/versioned_docs/version-0.12 (stable)/compiler/guides/debugger.md b/versioned_docs/version-0.12 (stable)/compiler/guides/debugger.md new file mode 100644 index 0000000..9516fde --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/compiler/guides/debugger.md @@ -0,0 +1,230 @@ +--- +title: Debugging programs +sidebar_position: 5 +--- + +# Debugging programs + +A very useful tool in the Miden compiler suite, is its TUI-based interactive debugger, accessible +via the `midenc debug` command. + +:::warning + +The debugger is still quite new, and while very useful already, still has a fair number of +UX annoyances. Please report any bugs you encounter, and we'll try to get them patched ASAP! + +::: + +## Getting started + +The debugger is launched by executing `midenc debug`, and giving it a path to a program compiled +by `midenc compile`. See [Program Inputs](#program-inputs) for information on how to provide inputs +to the program you wish to debug. Run `midenc help debug` for more detailed usage documentation. + +The debugger may also be used as a library, but that is left as an exercise for the reader for now. + +## Example + +```shell +# Compile a program to MAST from a rustc-generated Wasm module +midenc compile foo.wasm -o foo.masl + +# Load that program into the debugger and start executing it +midenc debug foo.masl +``` + +## Program inputs + +To pass arguments to the program on the operand stack, or via the advice provider, you have two +options, depending on the needs of the program: + +1. Pass arguments to `midenc debug` in the same order you wish them to appear on the stack. That + is, the first argument you specify will be on top of the stack, and so on. +2. Specify a configuration file from which to load inputs for the program, via the `--inputs` option. + +### Via command line + +To specify the contents of the operand stack, you can do so following the raw arguments separator `--`. +Each operand must be a valid field element value, in either decimal or hexadecimal format. For example: + +```shell +midenc debug foo.masl -- 1 2 0xdeadbeef +``` + +If you pass arguments via the command line in conjunction with `--inputs`, then the command line arguments +will be used instead of the contents of the `inputs.stack` option (if set). This lets you specify a baseline +set of inputs, and then try out different arguments using the command line. + +### Via inputs config + +While simply passing operands to the `midenc debug` command is useful, it only allows you to specify +inputs to be passed via operand stack. To provide inputs via the advice provider, you will need to use +the `--inputs` option. The configuration file expected by `--inputs` also lets you tweak the execution +options for the VM, such as the maximum and expected cycle counts. + +An example configuration file looks like so: + +```toml +# This section is used for execution options +[options] +max_cycles = 5000 +expected_cycles = 4000 + +# This section is the root table for all inputs +[inputs] +# Specify elements to place on the operand stack, leftmost element will be on top of the stack +stack = [1, 2, 0xdeadbeef] + +# This section contains input options for the advice provider +[inputs.advice] +# Specify elements to place on the advice stack, leftmost element will be on top +stack = [1, 2, 3, 4] + +# The `inputs.advice.map` section is a list of advice map entries that should be +# placed in the advice map before the program is executed. Entries with duplicate +# keys are handled on a last-write-wins basis. +[[inputs.advice.map]] +# The key for this entry in the advice map +digest = '0x3cff5b58a573dc9d25fd3c57130cc57e5b1b381dc58b5ae3594b390c59835e63' +# The values to be stored under this key +values = [1, 2, 3, 4] + +[[inputs.advice.map]] +digest = '0x20234ee941e53a15886e733cc8e041198c6e90d2a16ea18ce1030e8c3596dd38'' +values = [5, 6, 7, 8] +``` + +## Usage + +Once started, you will be dropped into the main debugger UI, stopped at the first cycle of +the program. The UI is organized into pages and panes, with the main/home page being the +one you get dropped into when the debugger starts. The home page contains the following panes: + +- Source Code - displays source code for the current instruction, if available, with + the relevant line and span highlighted, with syntax highlighting (when available) +- Disassembly - displays the 5 most recently executed VM instructions, and the current + cycle count +- Stack Trace - displays a stack trace for the current instruction, if the program was + compiled with tracing enabled. If frames are unavailable, this pane may be empty. +- Operand Stack - displays the contents of the operand stack and its current depth +- Breakpoints - displays the set of current breakpoints, along with how many were hit + at the current instruction, when relevant + +### Keyboard shortcuts + +On the home page, the following keyboard shortcuts are available: + +| Shortcut | Mnemonic | Description | +| -------- | -------------- | ------------------------------------------------------------------------------------------------------- | +| `q` | quit | exit the debugger | +| `h` | next pane | cycle focus to the next pane | +| `l` | prev pane | cycle focus to the previous pane | +| `s` | step | advance the VM one cycle | +| `n` | step next | advance the VM to the next instruction | +| `c` | continue | advance the VM to the next breakpoint, else to completion | +| `e` | exit frame | advance the VM until we exit the current call frame, a breakpoint is triggered, or execution terminates | +| `d` | delete | delete an item (where applicable, e.g. the breakpoints pane) | +| `:` | command prompt | bring up the command prompt (see below for details) | + +When various panes have focus, additional keyboard shortcuts are available, in any pane +with a list of items, or multiple lines (e.g. source code), `j` and `k` (or the up and +down arrows) will select the next item up and down, respectively. As more features are +added, I will document their keyboard shortcuts below. + +### Commands + +From the home page, typing `:` will bring up the command prompt in the footer pane. + +You will know the prompt is active because the keyboard shortcuts normally shown there will +no longer appear, and instead you will see the prompt, starting with `:`. It supports any +of the following commands: + +| Command | Aliases | Action | Description | +| ------------ | ------------ | ----------------- | --------------------------------------------------------------------- | +| `quit` | `q` | quit | exit the debugger | +| `debug` | | show debug log | display the internal debug log for the debugger itself | +| `reload` | | reload program | reloads the program from disk, and resets the UI (except breakpoints) | +| `breakpoint` | `break`, `b` | create breakpoint | see [Breakpoints](#breakpoints) | +| `read` | `r` | read memory | inspect linear memory (see [Reading Memory](#reading-memory) | + +## Breakpoints + +One of the most common things you will want to do with the debugger is set and manage breakpoints. +Using the command prompt, you can create breakpoints by typing `b` (or `break` or `breakpoint`), +followed by a space, and then the desired breakpoint expression to do any of the following: + +- Break at an instruction which corresponds to a source file (or file and line) whose name/path + matches a pattern +- Break at the first instruction which causes a call frame to be pushed for a procedure whose name + matches a pattern +- Break any time a specific opcode is executed +- Break at the next instruction +- Break after N cycles +- Break at CYCLE + +The syntax for each of these can be found below, in the same order (shown using `b` as the command): + +| Expression | Description | +| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | +| `b FILE[:LINE]` | Break when an instruction with a source location in `FILE` (a glob pattern)
_and_ that occur on `LINE` (literal, if provided) are hit. | +| `b in NAME` | Break when the glob pattern `NAME` matches the fully-qualified procedure name
containing the current instruction | +| `b for OPCODE` | Break when the an instruction with opcode `OPCODE` is exactly matched
(including immediate values) | +| `b next` | Break on the next instruction | +| `b after N` | Break after `N` cycles | +| `b at CYCLE` | Break when the cycle count reaches `CYCLE`.
If `CYCLE` has already occurred, this has no effect | + +When a breakpoint is hit, it will be highlighted, and the breakpoint window will display the number +of hit breakpoints in the lower right. + +After a breakpoint is hit, it expires if it is one of the following types: + +- Break after N +- Break at CYCLE +- Break next + +When a breakpoint expires, it is removed from the breakpoint list on the next cycle. + +## Reading memory + +Another useful diagnostic task is examining the contents of linear memory, to verify that expected +data has been written. You can do this via the command prompt, using `r` (or `read`), followed by +a space, and then the desired memory address and options: + +The format for read expressions is `:r ADDR [OPTIONS..]`, where `ADDR` is a memory address in +decimal or hexadecimal format (the latter requires the `0x` prefix). The `read` command supports +the following for `OPTIONS`: + +| Option | Alias | Values | Default | Description | +| ---------------- | ----- | --------------------------------------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------- | +| `-mode MODE` | `-m` | `words` (`word`, `w`), `bytes` (`byte`, `b`) | `words` | Specify a memory addressing mode | +| `-format FORMAT` | `-f` | `decimal` (`d`), `hex` (`x`), `binary` (`bin`, `b`) | `decimal` | Specify the format used to print integral values | +| `-count N` | `-c` | | `1` | Specify the number of units to read | +| `-type TYPE` | `-t` | See [Types](#types) | `word` | Specify the type of value to read
This also has the effect of modifying the default `-format` and unit size for `-count` | + +Any invalid combination of options, or invalid syntax, will display an error in the status bar. + +### Types + +| Type | Description | +| ------------------ | -------------------------------------------------- | +| `iN` | A signed integer of `N` bits | +| `uN` | An unsigned integer of `N` bits | +| `felt` | A field element | +| `word` | A Miden word, i.e. an array of four field elements | +| `ptr` or `pointer` | A 32-bit memory address (implies `-format hex`) | + +## Roadmap + +The following are some features planned for the near future: + +- **Watchpoints**, i.e. cause execution to break when a memory store touches a specific address +- **Conditional breakpoints**, i.e. only trigger a breakpoint when an expression attached to it + evaluates to true +- More DYIM-style breakpoints, i.e. when breaking on first hitting a match for a file or + procedure, we probably shouldn't continue to break for every instruction to which that + breakpoint technically applies. Instead, it would make sense to break and then temporarily + disable that breakpoint until something changes that would make breaking again useful. + This will rely on the ability to disable breakpoints, not delete them, which we don't yet + support. +- More robust type support in the `read` command +- Display procedure locals and their contents in a dedicated pane diff --git a/versioned_docs/version-0.12 (stable)/compiler/guides/develop_miden_in_rust.md b/versioned_docs/version-0.12 (stable)/compiler/guides/develop_miden_in_rust.md new file mode 100644 index 0000000..2af0205 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/compiler/guides/develop_miden_in_rust.md @@ -0,0 +1,45 @@ +--- +title: Developing Miden Programs in Rust +sidebar_position: 3 +--- + +# Developing Miden programs in Rust + +This chapter will walk through how to develop Miden programs in Rust using the standard library +provided by the `miden-stdlib-sys` crate (see the +[README](https://github.com/0xMiden/compiler/blob/main/sdk/stdlib-sys/README.md). + +## Getting started + +Import the standard library from the `miden-stdlib-sys` crate: + +```rust +use miden_stdlib_sys::*; +``` + +## Using `Felt` (field element) type + +The `Felt` type is a field element type that is used to represent the field element values of the +Miden VM. + +To initialize a `Felt` value from an integer constant checking the range at compile time, use the +`felt!` macro: + +```rust +let a = felt!(42); +``` + +Otherwise, use the `Felt::new` constructor: + +```rust +let a = Felt::new(some_integer_var).unwrap(); +``` + +The constructor returns an error if the value is not a valid field element, e.g. if it is not in the +range `0..=M` where `M` is the modulus of the field (2^64 - 2^32 + 1). + +The `Felt` type implements the standard arithmetic operations, e.g. addition, subtraction, +multiplication, division, etc. which are accessible through the standard Rust operators `+`, `-`, +`*`, `/`, etc. All arithmetic operations are wrapping, i.e. performed modulo `M`. + +TODO: Add examples of using operations on `Felt` type and available functions (`assert*`, etc.). diff --git a/versioned_docs/version-0.12 (stable)/compiler/guides/develop_miden_rollup_accounts_and_note_scripts_in_rust.md b/versioned_docs/version-0.12 (stable)/compiler/guides/develop_miden_rollup_accounts_and_note_scripts_in_rust.md new file mode 100644 index 0000000..ca0e632 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/compiler/guides/develop_miden_rollup_accounts_and_note_scripts_in_rust.md @@ -0,0 +1,8 @@ +--- +title: Developing Miden Rollup Accounts and Note Scripts in Rust +sidebar_position: 4 +--- + +# Developing Miden rollup accounts and note scripts in Rust + +This chapter walks you through how to develop Miden rollup accounts and note scripts in Rust using the Miden SDK crate. diff --git a/versioned_docs/version-0.12 (stable)/compiler/guides/index.md b/versioned_docs/version-0.12 (stable)/compiler/guides/index.md new file mode 100644 index 0000000..413bb0a --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/compiler/guides/index.md @@ -0,0 +1,13 @@ +--- +title: Guides +sidebar_position: 1 +--- + +# Guides + +This section contains practical guides for working with the Miden compiler: + +* [Compiling Rust to WebAssembly](./rust_to_wasm.md) - Walks through compiling Rust code to WebAssembly modules +* [WebAssembly to Miden Assembly](./wasm_to_masm.md) - Explains compiling Wasm modules to Miden Assembly +* [Developing Miden Programs in Rust](./develop_miden_in_rust.md) - Demonstrates using Rust with Miden's standard library +* [Developing Miden Rollup Accounts and Note Scripts in Rust](./develop_miden_rollup_accounts_and_note_scripts_in_rust.md) - Shows Rust development for Miden rollup components diff --git a/versioned_docs/version-0.12 (stable)/compiler/guides/rust_to_wasm.md b/versioned_docs/version-0.12 (stable)/compiler/guides/rust_to_wasm.md new file mode 100644 index 0000000..bdc41e5 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/compiler/guides/rust_to_wasm.md @@ -0,0 +1,181 @@ +--- +title: Rust to WebAssembly +sidebar_position: 1 +--- + +# Compiling Rust To WebAssembly + +This chapter will walk you through compiling a Rust crate to a WebAssembly (Wasm) module +in binary (i.e. `.wasm`) form. The Miden compiler has a frontend which can take such +modules and compile them on to Miden Assembly, which will be covered in the next chapter. + +## Setup + +First, let's set up a simple Rust project that contains an implementation of the Fibonacci +function (I know, it's overdone, but we're trying to keep things as simple as possible to +make it easier to show the results at each step, so bear with me): + +Start by creating a new library crate: + + cargo new --lib wasm-fib && cd wasm-fib + +To compile to WebAssembly, you must have the appropriate Rust toolchain installed, so let's add +a toolchain file to our project root so that `rustup` and `cargo` will know what we need, and use +them by default: + + cat <<EOF > rust-toolchain.toml + [toolchain] + channel = "stable" + targets = ["wasm32-wasip1"] + EOF + +Next, edit the `Cargo.toml` file as follows: + +```toml +[package] +name = "wasm-fib" +version = "0.1.0" +edition = "2021" + +[lib] +# Build this crate as a self-contained, C-style dynamic library +# This is required to emit the proper Wasm module type +crate-type = ["cdylib"] + +[dependencies] +# Use a tiny allocator in place of the default one, if we want +# to make use of types in the `alloc` crate, e.g. String. We +# don't need that now, but it's good information to have in hand. +#miden-sdk-alloc = "0.0.5" + +# When we build for Wasm, we'll use the release profile +[profile.release] +# Explicitly disable panic infrastructure on Wasm, as +# there is no proper support for them anyway, and it +# ensures that panics do not pull in a bunch of standard +# library code unintentionally +panic = "abort" +# Enable debug information so that we get useful debugging output +debug = true +# Optimize the output for size +opt-level = "z" +``` + +Most of these things are done to keep the generated code size as small as possible. Miden is a target +where the conventional wisdom about performance should be treated very carefully: we're almost always +going to benefit from less code, even if conventionally that code would be less efficient, simply due +to the difference in proving time accumulated due to extra instructions. That said, there are no hard +and fast rules, but these defaults are good ones to start with. + +:::tip + +We reference a simple bump allocator provided by `miden-sdk-alloc` above, but any simple +allocator will do. The trade offs made by these small allocators are not generally suitable for +long-running, or allocation-heavy applications, as they "leak" memory (generally because they +make little to no attempt to recover freed allocations), however they are very useful for +one-shot programs that do minimal allocation, which is going to be the typical case for Miden +programs. + +::: + +Next, edit `src/lib.rs` as shown below: + +```rust +// Do not link against libstd (i.e. anything defined in `std::`) +#![no_std] + +// However, we could still use some standard library types while +// remaining no-std compatible, if we uncommented the following lines: +// +// extern crate alloc; +// use alloc::{string::String, vec::Vec}; + +// If we wanted to use the types mentioned above, it would also be +// a good idea to use the allocator we pulled in as a dependency +// in Cargo.toml, like so: +//#[global_allocator] +//static ALLOC: miden_sdk_alloc::BumpAlloc = miden_sdk_alloc::BumpAlloc::new(); + +// Required for no-std crates +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + // Compiles to a trap instruction in WebAssembly + core::arch::wasm32::unreachable() +} + +// Marking the function no_mangle ensures that it is exported +// from the compiled binary as `fib`, otherwise it would have +// a mangled name that has no stable form. +// +// You can specify a different name from the library than the +// name in the source code using the `#[export_name = "foo"]` +// attribute, which will make the function callable as `foo` +// externally (in this example) +#[no_mangle] +pub fn fib(n: u32) -> u32 { + let mut a = 0; + let mut b = 1; + for _ in 0..n { + let c = a + b; + a = b; + b = c; + } + a +} +``` + +This exports our `fib` function from the library, making it callable from within a larger Miden program. + +All that remains is to compile to WebAssembly: + + cargo build --release --target=wasm32-wasip1 + +This places a `wasm_fib.wasm` file under the `target/wasm32-wasip1/release/` directory, which +we can then examine with [wasm2wat](https://github.com/WebAssembly/wabt) to set the code we generated: + + wasm2wat target/wasm32-wasip1/release/wasm_fib.wasm + +Which dumps the following output (may differ slightly on your machine, depending on the specific compiler version): + +```wat +(module $wasm_fib.wasm + (type (;0;) (func (param i32) (result i32))) + (func $fib (type 0) (param i32) (result i32) + (local i32 i32 i32) + i32.const 0 + local.set 1 + i32.const 1 + local.set 2 + loop (result i32) ;; label = @1 + local.get 2 + local.set 3 + block ;; label = @2 + local.get 0 + br_if 0 (;@2;) + local.get 1 + return + end + local.get 0 + i32.const -1 + i32.add + local.set 0 + local.get 1 + local.get 3 + i32.add + local.set 2 + local.get 3 + local.set 1 + br 0 (;@1;) + end) + (memory (;0;) 16) + (global $__stack_pointer (mut i32) (i32.const 1048576)) + (export "memory" (memory 0)) + (export "fib" (func $fib))) +``` + +Success! + +## Next steps + +In [Compiling WebAssembly to Miden Assembly](wasm_to_masm.md), we walk through how to take the +WebAssembly module we just compiled, and lower it to Miden Assembly using `midenc`! diff --git a/versioned_docs/version-0.12 (stable)/compiler/guides/wasm_to_masm.md b/versioned_docs/version-0.12 (stable)/compiler/guides/wasm_to_masm.md new file mode 100644 index 0000000..8b6bb44 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/compiler/guides/wasm_to_masm.md @@ -0,0 +1,143 @@ +--- +title: WebAssembly to Miden Assembly +sidebar_position: 2 +--- + +# Compiling WebAssembly to Miden Assembly + +This guide will walk you through compiling a WebAssembly (Wasm) module, in binary form +(i.e. a `.wasm` file), to Miden Assembly (Masm), both in its binary package form (a `.masp` file), +and in textual Miden Assembly syntax form (i.e. a `.masm` file). + +## Setup + +We will be making use of the example crate we created in [Compiling Rust to WebAssembly](rust_to_wasm.md), +which produces a small Wasm module that is easy to examine in Wasm text format, and demonstrates a +good set of default choices for a project compiling to Miden Assembly from Rust. + +In this chapter, we will be compiling Wasm to Masm using the `midenc` executable, so ensure that +you have followed the instructions in the [Getting Started with `midenc`](../usage/midenc.md) guide +and then return here. + +:::note + +While we are using `midenc` for this guide, the more common use case will be to use the +`cargo-miden` Cargo extension to handle the gritty details of compiling from Rust to Wasm +for you. However, the purpose of this guide is to show you what `cargo-miden` is handling +for you, and to give you a foundation for using `midenc` yourself if needed. + +::: + +## Compiling to Miden Assembly + +In the last chapter, we compiled a Rust crate to WebAssembly that contains an implementation +of the Fibonacci function called `fib`, that was emitted to +`target/wasm32-wasip1/release/wasm_fib.wasm`. All that remains is to tell `midenc` to compile this +module to Miden Assembly. + +Currently, by default, the compiler will emit an experimental package format that the Miden VM does +not yet support. We will instead use `midenc run` to execute the package using the VM for us, but +once the package format is stabilized, this same approach will work with `miden run` as well. + +We also want to examine the Miden Assembly generated by the compiler, so we're going to ask the +compiler to emit both types of artifacts: + +```bash +midenc compile --emit masm=wasm_fib.masm,masp target/wasm32-wasip1/release/wasm_fib.wasm +``` + +This will compile our Wasm module to a Miden package with the `.masp` extension, and also emit the +textual Masm to `wasm_fib.masm` so we can review it. The `wasm_fib.masp` file will be emitted in the +default output directory, which is the current working directory by default. + +If we dump the contents of `wasm_fib.masm`, we'll see the following generated code: + +```masm +export.fib + push.0 + push.1 + movup.2 + swap.1 + dup.1 + neq.0 + push.1 + while.true + if.true + push.4294967295 + movup.2 + swap.1 + u32wrapping_add + dup.1 + swap.1 + swap.3 + swap.1 + u32wrapping_add + movup.2 + swap.1 + dup.1 + neq.0 + push.1 + else + drop + drop + push.0 + end + end +end +``` + +If you compare this to the WebAssembly text format, you can see that this is a fairly +faithful translation, but there may be areas where we generate sub-optimal Miden Assembly. + +:::note + +At the moment the compiler does only minimal optimization, late in the pipeline during codegen, +and only in an effort to minimize operand stack management code. So if you see an instruction +sequence you think is bad, bring it to our attention, and if it is something that we can solve +as part of our overall optimization efforts, we will be sure to do so. There _are_ limits to +what we can generate compared to what one can write by hand, particularly because Rust's +memory model requires us to emulate byte-addressable memory on top of Miden's word-addressable +memory, however our goal is to keep this overhead within an acceptable bound in the general case, +and easily-recognized patterns that can be simplified using peephole optimization are precisely +the kind of thing we'd like to know about, as those kinds of optimizations are likely to produce +the most significant wins. + +::: + +## Testing with the Miden VM + +:::note + +Because the compiler ships with the VM embedded for `midenc debug`, you can run your program +without having to install the VM separately, though you should do that as well, as `midenc` only +exposes a limited set of commands for executing programs, intended for debugging. + +::: + +We can test our compiled program like so: + +```bash +$ midenc run --num-outputs 1 wasm_fib.masp -- 10 +============================================================ +Run program: wasm_fib.masp +============================================================ +Executed program with hash 0xe5ba88695040ec2477821b26190e9addbb1c9571ae30c564f5bbfd6cabf6c535 in 19 milliseconds +Output: [55] +VM cycles: 295 extended to 512 steps (42% padding). +├── Stack rows: 295 +├── Range checker rows: 67 +└── Chiplets rows: 250 +├── Hash chiplet rows: 248 +├── Bitwise chiplet rows: 0 +├── Memory chiplet rows: 1 +└── Kernel ROM rows: 0 +``` + +Success! We got the expected result of `55`. + +## Next steps + +This guide is not comprehensive, as we have not yet examined in detail the differences between +compiling libraries vs programs, linking together multiple libraries, packages, or discussed some of +the more esoteric compiler options. We will be updating this documentation with those details and +more in the coming weeks and months, so bear with us while we flesh out our guides! diff --git a/versioned_docs/version-0.12 (stable)/compiler/index.md b/versioned_docs/version-0.12 (stable)/compiler/index.md new file mode 100644 index 0000000..d309639 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/compiler/index.md @@ -0,0 +1,106 @@ +--- +title: Compiler +sidebar_position: 1 +--- + +# Getting Started + +Welcome to the documentation for the Miden compiler toolchain. + +:::caution + +The compiler is currently in an experimental state, and has known bugs and limitations, it is +not yet ready for production usage. However, we'd encourage you to start experimenting with it +yourself, and give us feedback on any issues or sharp edges you encounter. + +::: + +The documentation found here should provide a good starting point for the current capabilities of +the toolchain, however if you find something that is not covered, but is not listed as +unimplemented or a known limitation, please let us know by reporting an issue on the compiler +[issue tracker](https://github.com/0xMiden/compiler/issues). + +## What is provided? + +The compiler toolchain consists of the following primary components: + +- An intermediate representation (IR), which can be lowered to by compiler backends wishing to + support Miden as a target. The Miden IR is an SSA IR, much like Cranelift or LLVM, providing a + much simpler path from any given source language (e.g. Rust), to Miden Assembly. It is used + internally by the rest of the Miden compiler suite. +- A WebAssembly (Wasm) frontend for Miden IR. It can handle lowering both core Wasm modules, as + well as basic components using the experimental WebAssembly Component Model. Currently, the Wasm + frontend is known to work with Wasm modules produced by `rustc`, which is largely just what LLVM + produces, but with the shadow stack placed at the start of linear memory rather than after + read-only data. In the future we intend to support more variety in the structure of Wasm modules + we accept, but for the time being we're primarily focused on using this as the path for lowering + Rust to Miden. +- The compiler driver, in the form of the `midenc` executable, and a Rust crate, `midenc-compiler` + to allow integrating the compiler into other tools. This plays the same role as `rustc` does in + the Rust ecosystem. +- A Cargo extension, `cargo-miden`, that provides a convenient developer experience for creating + and compiling Rust projects targeting Miden. It contains a project template for a basic Rust crate, + and handles orchestrating `rustc` and `midenc` to compile the crate to WebAssembly, and then to + Miden Assembly. +- A terminal-based interactive debugger, available via `midenc debug`, which provides a UI very + similar to `lldb` or `gdb` when using the TUI mode. You can use this to run a program, or step + through it cycle-by-cycle. You can set various types of breakpoints; see the source code, call + stack, and contents of the operand stack at the current program point; as well as interatively + read memory and format it in various ways for display. +- A Miden SDK for Rust, which provides types and bindings to functionality exported from the Miden + standard library, as well as the Miden transaction kernel API. You can use this to access native + Miden features which are not provided by Rust out-of-the-box. The project template generated by + `cargo miden new` automatically adds this as a dependency. + +## What can I do with it? + +That all sounds great, but what can you do with the compiler today? The answer depends a bit on what +aspect of the compiler you are interested in: + +### Rust + +The most practically useful, and interesting capability provided by the compiler currently, is the +ability to compile arbitrary Rust programs to Miden Assembly. See the guides for more information +on setting up and compiling a Rust crate for execution via Miden. + +### WebAssembly + +More generally, the compiler frontend is capable of compiling WebAssembly modules, with some +constraints, to Miden Assembly. As a result, it is possible to compile a wider variety of languages +to Miden Assembly than just Rust, so long as the language can compile to WebAssembly. However, we +do not currently provide any of the language-level support for languages other than Rust, and +have limited ability to provide engineering support for languages other than Rust at this time. + +Our Wasm frontend does not support all of the extensions to the WebAssembly MVP, most notably the +reference types and GC proposals. + +### Miden IR + +If you are interested in compiling to Miden from your own compiler, you can target Miden IR, and +invoke the driver from your compiler to emit Miden artifacts. At this point in time, we don't have +the resources to provide much in the way of engineering support for this use case, but if you find +issues in your efforts to use the IR in your compiler, we would certainly like to know about them! + +We do not currently perform any optimizations on the IR, since we are primarily working with the +output of compiler backends which have already applied optimizations, at this time. This may change +in the future, but for now it is expected that you implement your own optimization passes as needed. + +## Known bugs and limitations + +For the latest information on known bugs and limitations, see the [issue tracker](https://github.com/0xMiden/compiler/issues). + +## Where to start? + +Provided here are a set of guides which are focused on documenting a couple of supported workflows +we expect will meet the needs of most users, within the constraints of the current feature set of +the compiler. If you find that there is something you wish to do that is not covered, and is not +one of our known limitations, please open an issue, and we will try to address the missing docs as +soon as possible. + +## Installation + +To get started, there are a few ways you might use the Miden compiler. Select the one that applies +to you, and the corresponding guide will walk you through getting up and running: + +1. [Using the Cargo extension](usage/cargo-miden.md) +2. [Using the `midenc` executable](usage/midenc.md) diff --git a/versioned_docs/version-0.12 (stable)/compiler/theme/Admonition/Icon/Danger.tsx b/versioned_docs/version-0.12 (stable)/compiler/theme/Admonition/Icon/Danger.tsx new file mode 100644 index 0000000..ab14d7d --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/compiler/theme/Admonition/Icon/Danger.tsx @@ -0,0 +1,19 @@ +import React, { type ReactNode } from "react"; +import type { Props } from "@theme/Admonition/Icon/Danger"; + +export default function AdmonitionIconDanger(props: Props): ReactNode { + return ( + + + + ); +} diff --git a/versioned_docs/version-0.12 (stable)/compiler/theme/Admonition/Icon/Info.tsx b/versioned_docs/version-0.12 (stable)/compiler/theme/Admonition/Icon/Info.tsx new file mode 100644 index 0000000..59e48a5 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/compiler/theme/Admonition/Icon/Info.tsx @@ -0,0 +1,20 @@ +import React, { type ReactNode } from "react"; +import type { Props } from "@theme/Admonition/Icon/Info"; + +export default function AdmonitionIconInfo(props: Props): ReactNode { + return ( + + + + ); +} diff --git a/versioned_docs/version-0.12 (stable)/compiler/theme/Admonition/Icon/Note.tsx b/versioned_docs/version-0.12 (stable)/compiler/theme/Admonition/Icon/Note.tsx new file mode 100644 index 0000000..d7c524b --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/compiler/theme/Admonition/Icon/Note.tsx @@ -0,0 +1,20 @@ +import React, { type ReactNode } from "react"; +import type { Props } from "@theme/Admonition/Icon/Note"; + +export default function AdmonitionIconNote(props: Props): ReactNode { + return ( + + + + ); +} diff --git a/versioned_docs/version-0.12 (stable)/compiler/theme/Admonition/Icon/Tip.tsx b/versioned_docs/version-0.12 (stable)/compiler/theme/Admonition/Icon/Tip.tsx new file mode 100644 index 0000000..219bb8d --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/compiler/theme/Admonition/Icon/Tip.tsx @@ -0,0 +1,20 @@ +import React, { type ReactNode } from "react"; +import type { Props } from "@theme/Admonition/Icon/Tip"; + +export default function AdmonitionIconTip(props: Props): ReactNode { + return ( + + + + ); +} diff --git a/versioned_docs/version-0.12 (stable)/compiler/theme/Admonition/Icon/Warning.tsx b/versioned_docs/version-0.12 (stable)/compiler/theme/Admonition/Icon/Warning.tsx new file mode 100644 index 0000000..f96398d --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/compiler/theme/Admonition/Icon/Warning.tsx @@ -0,0 +1,20 @@ +import React, { type ReactNode } from "react"; +import type { Props } from "@theme/Admonition/Icon/Warning"; + +export default function AdmonitionIconCaution(props: Props): ReactNode { + return ( + + + + ); +} diff --git a/versioned_docs/version-0.12 (stable)/compiler/theme/Admonition/Layout/index.tsx b/versioned_docs/version-0.12 (stable)/compiler/theme/Admonition/Layout/index.tsx new file mode 100644 index 0000000..7b2c170 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/compiler/theme/Admonition/Layout/index.tsx @@ -0,0 +1,51 @@ +import React, { type ReactNode } from "react"; +import clsx from "clsx"; +import { ThemeClassNames } from "@docusaurus/theme-common"; + +import type { Props } from "@theme/Admonition/Layout"; + +import styles from "./styles.module.css"; + +function AdmonitionContainer({ + type, + className, + children, +}: Pick & { children: ReactNode }) { + return ( +
+ {children} +
+ ); +} + +function AdmonitionHeading({ icon, title }: Pick) { + return ( +
+ {icon} + {/* {title} */} +
+ ); +} + +function AdmonitionContent({ children }: Pick) { + return children ? ( +
{children}
+ ) : null; +} + +export default function AdmonitionLayout(props: Props): ReactNode { + const { type, icon, title, children, className } = props; + return ( + + {title || icon ? : null} + {children} + + ); +} diff --git a/versioned_docs/version-0.12 (stable)/compiler/theme/Admonition/Layout/styles.module.css b/versioned_docs/version-0.12 (stable)/compiler/theme/Admonition/Layout/styles.module.css new file mode 100644 index 0000000..88df7e6 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/compiler/theme/Admonition/Layout/styles.module.css @@ -0,0 +1,35 @@ +.admonition { + margin-bottom: 1em; +} + +.admonitionHeading { + font: var(--ifm-heading-font-weight) var(--ifm-h5-font-size) / + var(--ifm-heading-line-height) var(--ifm-heading-font-family); + text-transform: uppercase; +} + +/* Heading alone without content (does not handle fragment content) */ +.admonitionHeading:not(:last-child) { + margin-bottom: 0.3rem; +} + +.admonitionHeading code { + text-transform: none; +} + +.admonitionIcon { + display: inline-block; + vertical-align: middle; + margin-right: 0.4em; +} + +.admonitionIcon svg { + display: inline-block; + height: 1.6em; + width: 1.6em; + fill: var(--ifm-alert-foreground-color); +} + +.admonitionContent > :last-child { + margin-bottom: 0; +} diff --git a/versioned_docs/version-0.12 (stable)/compiler/theme/Admonition/Type/Caution.tsx b/versioned_docs/version-0.12 (stable)/compiler/theme/Admonition/Type/Caution.tsx new file mode 100644 index 0000000..b570a37 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/compiler/theme/Admonition/Type/Caution.tsx @@ -0,0 +1,32 @@ +import React, {type ReactNode} from 'react'; +import clsx from 'clsx'; +import Translate from '@docusaurus/Translate'; +import type {Props} from '@theme/Admonition/Type/Caution'; +import AdmonitionLayout from '@theme/Admonition/Layout'; +import IconWarning from '@theme/Admonition/Icon/Warning'; + +const infimaClassName = 'alert alert--warning'; + +const defaultProps = { + icon: , + title: ( + + caution + + ), +}; + +// TODO remove before v4: Caution replaced by Warning +// see https://github.com/facebook/docusaurus/issues/7558 +export default function AdmonitionTypeCaution(props: Props): ReactNode { + return ( + + {props.children} + + ); +} diff --git a/versioned_docs/version-0.12 (stable)/compiler/theme/Admonition/Type/Danger.tsx b/versioned_docs/version-0.12 (stable)/compiler/theme/Admonition/Type/Danger.tsx new file mode 100644 index 0000000..49901fa --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/compiler/theme/Admonition/Type/Danger.tsx @@ -0,0 +1,30 @@ +import React, {type ReactNode} from 'react'; +import clsx from 'clsx'; +import Translate from '@docusaurus/Translate'; +import type {Props} from '@theme/Admonition/Type/Danger'; +import AdmonitionLayout from '@theme/Admonition/Layout'; +import IconDanger from '@theme/Admonition/Icon/Danger'; + +const infimaClassName = 'alert alert--danger'; + +const defaultProps = { + icon: , + title: ( + + danger + + ), +}; + +export default function AdmonitionTypeDanger(props: Props): ReactNode { + return ( + + {props.children} + + ); +} diff --git a/versioned_docs/version-0.12 (stable)/compiler/theme/Admonition/Type/Info.tsx b/versioned_docs/version-0.12 (stable)/compiler/theme/Admonition/Type/Info.tsx new file mode 100644 index 0000000..018e0a1 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/compiler/theme/Admonition/Type/Info.tsx @@ -0,0 +1,30 @@ +import React, {type ReactNode} from 'react'; +import clsx from 'clsx'; +import Translate from '@docusaurus/Translate'; +import type {Props} from '@theme/Admonition/Type/Info'; +import AdmonitionLayout from '@theme/Admonition/Layout'; +import IconInfo from '@theme/Admonition/Icon/Info'; + +const infimaClassName = 'alert alert--info'; + +const defaultProps = { + icon: , + title: ( + + info + + ), +}; + +export default function AdmonitionTypeInfo(props: Props): ReactNode { + return ( + + {props.children} + + ); +} diff --git a/versioned_docs/version-0.12 (stable)/compiler/theme/Admonition/Type/Note.tsx b/versioned_docs/version-0.12 (stable)/compiler/theme/Admonition/Type/Note.tsx new file mode 100644 index 0000000..c99e038 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/compiler/theme/Admonition/Type/Note.tsx @@ -0,0 +1,30 @@ +import React, {type ReactNode} from 'react'; +import clsx from 'clsx'; +import Translate from '@docusaurus/Translate'; +import type {Props} from '@theme/Admonition/Type/Note'; +import AdmonitionLayout from '@theme/Admonition/Layout'; +import IconNote from '@theme/Admonition/Icon/Note'; + +const infimaClassName = 'alert alert--secondary'; + +const defaultProps = { + icon: , + title: ( + + note + + ), +}; + +export default function AdmonitionTypeNote(props: Props): ReactNode { + return ( + + {props.children} + + ); +} diff --git a/versioned_docs/version-0.12 (stable)/compiler/theme/Admonition/Type/Tip.tsx b/versioned_docs/version-0.12 (stable)/compiler/theme/Admonition/Type/Tip.tsx new file mode 100644 index 0000000..18604a5 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/compiler/theme/Admonition/Type/Tip.tsx @@ -0,0 +1,30 @@ +import React, {type ReactNode} from 'react'; +import clsx from 'clsx'; +import Translate from '@docusaurus/Translate'; +import type {Props} from '@theme/Admonition/Type/Tip'; +import AdmonitionLayout from '@theme/Admonition/Layout'; +import IconTip from '@theme/Admonition/Icon/Tip'; + +const infimaClassName = 'alert alert--success'; + +const defaultProps = { + icon: , + title: ( + + tip + + ), +}; + +export default function AdmonitionTypeTip(props: Props): ReactNode { + return ( + + {props.children} + + ); +} diff --git a/versioned_docs/version-0.12 (stable)/compiler/theme/Admonition/Type/Warning.tsx b/versioned_docs/version-0.12 (stable)/compiler/theme/Admonition/Type/Warning.tsx new file mode 100644 index 0000000..61d9597 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/compiler/theme/Admonition/Type/Warning.tsx @@ -0,0 +1,30 @@ +import React, {type ReactNode} from 'react'; +import clsx from 'clsx'; +import Translate from '@docusaurus/Translate'; +import type {Props} from '@theme/Admonition/Type/Warning'; +import AdmonitionLayout from '@theme/Admonition/Layout'; +import IconWarning from '@theme/Admonition/Icon/Warning'; + +const infimaClassName = 'alert alert--warning'; + +const defaultProps = { + icon: , + title: ( + + warning + + ), +}; + +export default function AdmonitionTypeWarning(props: Props): ReactNode { + return ( + + {props.children} + + ); +} diff --git a/versioned_docs/version-0.12 (stable)/compiler/theme/Admonition/Types.tsx b/versioned_docs/version-0.12 (stable)/compiler/theme/Admonition/Types.tsx new file mode 100644 index 0000000..2a10019 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/compiler/theme/Admonition/Types.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import AdmonitionTypeNote from '@theme/Admonition/Type/Note'; +import AdmonitionTypeTip from '@theme/Admonition/Type/Tip'; +import AdmonitionTypeInfo from '@theme/Admonition/Type/Info'; +import AdmonitionTypeWarning from '@theme/Admonition/Type/Warning'; +import AdmonitionTypeDanger from '@theme/Admonition/Type/Danger'; +import AdmonitionTypeCaution from '@theme/Admonition/Type/Caution'; +import type AdmonitionTypes from '@theme/Admonition/Types'; + +const admonitionTypes: typeof AdmonitionTypes = { + note: AdmonitionTypeNote, + tip: AdmonitionTypeTip, + info: AdmonitionTypeInfo, + warning: AdmonitionTypeWarning, + danger: AdmonitionTypeDanger, +}; + +// Undocumented legacy admonition type aliases +// Provide hardcoded/untranslated retrocompatible label +// See also https://github.com/facebook/docusaurus/issues/7767 +const admonitionAliases: typeof AdmonitionTypes = { + secondary: (props) => , + important: (props) => , + success: (props) => , + caution: AdmonitionTypeCaution, +}; + +export default { + ...admonitionTypes, + ...admonitionAliases, +}; diff --git a/versioned_docs/version-0.12 (stable)/compiler/theme/Admonition/index.tsx b/versioned_docs/version-0.12 (stable)/compiler/theme/Admonition/index.tsx new file mode 100644 index 0000000..8f4225d --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/compiler/theme/Admonition/index.tsx @@ -0,0 +1,21 @@ +import React, {type ComponentType, type ReactNode} from 'react'; +import {processAdmonitionProps} from '@docusaurus/theme-common'; +import type {Props} from '@theme/Admonition'; +import AdmonitionTypes from '@theme/Admonition/Types'; + +function getAdmonitionTypeComponent(type: string): ComponentType { + const component = AdmonitionTypes[type]; + if (component) { + return component; + } + console.warn( + `No admonition component found for admonition type "${type}". Using Info as fallback.`, + ); + return AdmonitionTypes.info!; +} + +export default function Admonition(unprocessedProps: Props): ReactNode { + const props = processAdmonitionProps(unprocessedProps); + const AdmonitionTypeComponent = getAdmonitionTypeComponent(props.type); + return ; +} diff --git a/versioned_docs/version-0.12 (stable)/compiler/usage/_category_.yml b/versioned_docs/version-0.12 (stable)/compiler/usage/_category_.yml new file mode 100644 index 0000000..bb98c44 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/compiler/usage/_category_.yml @@ -0,0 +1,4 @@ +label: Usage +# Determines where this documentation section appears relative to other sections in the parent folder +position: 2 +collapsed: true diff --git a/versioned_docs/version-0.12 (stable)/compiler/usage/cargo-miden.md b/versioned_docs/version-0.12 (stable)/compiler/usage/cargo-miden.md new file mode 100644 index 0000000..944da6b --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/compiler/usage/cargo-miden.md @@ -0,0 +1,124 @@ +--- +title: As a Cargo extension +sidebar_position: 3 +--- + +# Getting started with Cargo + +As part of the Miden compiler toolchain, we provide a Cargo extension, `cargo-miden`, which provides +a template to spin up a new Miden project in Rust, and takes care of orchestrating `rustc` and +`midenc` to compile the Rust crate to a Miden package. + +## Installation + +:::warning + +Currently, `midenc` (and as a result, `cargo-miden`), requires the nightly Rust toolchain, so +make sure you have it installed first: + +```bash +rustup toolchain install nightly-2025-07-20 +``` + +NOTE: You can also use the latest nightly, but the specific nightly shown here is known to +work. + +::: + +To install the extension, clone the compiler repo first: + +```bash +git clone https://github.com/0xMiden/compiler +``` + +Then, run the following in your shell in the cloned repo folder: + +```bash +cargo install --path tools/cargo-miden --locked +``` + +This will take a minute to compile, but once complete, you can run `cargo help miden` or just +`cargo miden` to see the set of available commands and options. + +To get help for a specific command, use `cargo miden help ` or `cargo miden --help`. + +## Creating an example project + +If you're new to Miden and want to explore some example projects, you can use the `cargo miden example` command to create a project from one of our templates: + +```bash +cargo miden example +``` + +To see the list of available examples, run: + +```bash +cargo miden example --help +``` + +Available examples include: + +- **basic-wallet** - A basic wallet account implementation (creates both the wallet and a paired p2id-note) +- **p2id-note** - A pay-to-ID note script (creates both the note and a paired basic-wallet) +- **counter-contract** - A simple counter contract (creates both the contract and a paired counter-note) +- **counter-note** - A note script for interacting with the counter contract (creates both the note and paired counter-contract) +- **fibonacci** - A Fibonacci sequence calculator demonstrating basic computations +- **collatz** - An implementation of the Collatz conjecture +- **is-prime** - A prime number checker +- **storage-example** - Demonstrates storage operations in Miden + +Note that some examples create paired projects. For instance, running `cargo miden example basic-wallet` will create a directory containing both the `basic-wallet` account and the `p2id-note` script that interacts with it. + +## Creating a new project + +Your first step will be to create a new Rust project set up for compiling to Miden: + +```bash +cargo miden new foo +``` + +In this above example, this will create a new directory `foo`, containing a Cargo project for a +crate named `foo`, generated from our Miden project template. + +The template we use sets things up so that you can pretty much just build and run. Since the +toolchain depends on Rust's native WebAssembly target, it is set up just like a minimal WebAssembly +crate, with some additional tweaks for Miden specifically. + +Out of the box, you will get a Rust crate that depends on the Miden SDK, and sets the global +allocator to a simple bump allocator we provide as part of the SDK, and is well suited for most +Miden use cases, avoiding the overhead of more complex allocators. + +As there is no panic infrastructure, `panic = "abort"` is set, and the panic handler is configured +to use the native WebAssembly `unreachable` intrinsic, so the compiler will strip out all of the +usual panic formatting code. + +## Compiling to Miden package + +Now that you've created your project, compiling it to Miden package is as easy as running the +following command from the root of the project directory: + +```bash +cargo miden build --release +``` + +This will emit the compiled artifacts to `target/miden/release/foo.masp`. + +## Running a compiled Miden VM program + +:::warning + +To run the compiled Miden VM program you need to have `midenc` installed. See [`midenc` docs](./midenc.md) for the installation instructions. + +::: + +The compiled Miden VM program can be run from the Miden package with the following: + +```bash +midenc run target/miden/release/foo.masp --inputs some_inputs.toml +``` + +See `midenc run --help` for the inputs file format. + +## Examples + +Check out the [examples](https://github.com/0xMiden/compiler/tree/next/examples) for some `cargo-miden` project examples. diff --git a/versioned_docs/version-0.12 (stable)/compiler/usage/index.md b/versioned_docs/version-0.12 (stable)/compiler/usage/index.md new file mode 100644 index 0000000..10bbd76 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/compiler/usage/index.md @@ -0,0 +1,13 @@ +--- +title: Usage +sidebar_position: 1 +--- + +# Usage + +The Usage section documents how to work with Miden's compiler tools. Key components include: + +- **Command-line interface** ([`midenc`](./midenc.md)) - A low-level compiler driver offering precise + control over compilation outputs and diagnostic information +- **Cargo extension** ([`cargo-miden`](./cargo-miden.md)) - Higher-level build tool integration for + managing Miden projects within Rust's package ecosystem diff --git a/versioned_docs/version-0.12 (stable)/compiler/usage/midenc.md b/versioned_docs/version-0.12 (stable)/compiler/usage/midenc.md new file mode 100644 index 0000000..3fdab5b --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/compiler/usage/midenc.md @@ -0,0 +1,134 @@ +--- +title: As an Executable +sidebar_position: 2 +--- + +# Getting started with `midenc` + +The `midenc` executable is the command-line interface for the compiler driver, as well as other +helpful tools, such as the interactive debugger. + +While it is a lower-level tool compared to `cargo-miden`, just like the difference between `rustc` +and `cargo`, it provides a lot of functionality for emitting diagnostic information, controlling +the output of the compiler, and configuring the compilation pipeline. Most users will want to use +`cargo-miden`, but understanding `midenc` is helpful for those times where you need to get your +hands dirty. + +## Installation + +:::warning + +Currently, `midenc` (and as a result, `cargo-miden`), requires the nightly Rust toolchain, so +make sure you have it installed first: + +```bash +rustup toolchain install nightly-2025-07-20 +``` + +NOTE: You can also use the latest nightly, but the specific nightly shown here is known to +work. + +::: + +To install the `midenc`, clone the compiler repo first: + +```bash +git clone https://github.com/0xMiden/compiler +``` + +Then, run the following in your shell in the cloned repo folder: + +```bash +cargo install --path midenc --locked +``` + +## Usage + +Once installed, you should be able to invoke the compiler, you should see output similar to this: + +```bash +midenc help compile +Usage: midenc compile [OPTIONS] [-- ...] + +Arguments: + [INPUTS]... + Path(s) to the source file(s) to compile. + + You may also use `-` as a file name to read a file from stdin. + +Options: + --output-dir + Write all compiler artifacts to DIR + + -W + Modify how warnings are treated by the compiler + + [default: auto] + + Possible values: + - none: Disable all warnings + - auto: Enable all warnings + - error: Promotes warnings to errors + + -v, --verbose + When set, produces more verbose output during compilation + + -h, --help + Print help (see a summary with '-h') +``` + +The actual help output covers quite a bit more than shown here, this is just for illustrative +purposes. + +The `midenc` executable supports two primary functions at this time: + +- `midenc compile` to compile one of our supported input formats to Miden Assembly +- `midenc debug` to run a Miden program attached to an interactive debugger +- `midenc run` to run a Miden program non-interactively, equivalent to `miden run` + +## Compilation + +See the help output for `midenc compile` for detailed information on its options and their +behavior. However, the following is an example of how one might use `midenc compile` in practice: + +```bash +midenc compile --target rollup \ + --entrypoint 'foo::main' \ + -lextra \ + -L ./masm \ + --emit=hir=-,masp \ + -o out.masp \ + target/wasm32-wasip1/release/foo.wasm +``` + +In this scenario, we are in the root of a Rust crate, named `foo`, which we have compiled for the +`wasm32-wasip1` target, which placed the resulting WebAssembly module in the +`target/wasm32-wasip1/release` directory. This crate exports a function named `main`, which we want +to use as the entrypoint of the program. + +Additionally, our Rust code links against some hand-written Miden Assembly code, namespaced under +`extra`, which can be found in `./masm/extra`. We are telling `midenc` to link the `extra` library, +and to add the `./masm` directory to the library search path. + +Lastly, we're configuring the output: + +- We're using `--emit` to request `midenc` to dump Miden IR (`hir`) to stdout (specified via the `-` + shorthand), in addition to the Miden package artifact (`masp`). +- We're telling `midenc` to write the compiled output to `out.masp` in the current directory, rather + than the default path that would have been used (`target/miden/foo.masp`). + +## Debugging + +See [Debugging Programs](../guides/debugger.md) for details on using `midenc debug` to debug Miden programs. + +## Next steps + +We have put together two useful guides to walk through more detail on compiling Rust to WebAssembly: + +1. To learn how to compile Rust to WebAssembly so that you can invoke `midenc compile` on the + resulting Wasm module, see [this guide](../guides/rust_to_wasm.md). +2. If you already have a WebAssembly module, or know how to produce one, and want to learn how to + compile it to Miden Assembly, see [this guide](../guides/wasm_to_masm.md). + +You may also be interested in our [basic account project template](https://github.com/0xMiden/rust-templates/tree/main/account/template), +as a starting point for your own Rust project. diff --git a/versioned_docs/version-0.12 (stable)/faq.md b/versioned_docs/version-0.12 (stable)/faq.md new file mode 100644 index 0000000..cefcf9c --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/faq.md @@ -0,0 +1,101 @@ +# FAQ + +## How is privacy implemented in Miden? + +Miden leverages zero-knowledge proofs and client side execution and proving to provide security and privacy. + +## Does Miden support encrypted notes? + +At the moment, Miden does not have support for encrypted notes but it is a planned feature. + +## Why does Miden have delegated proving? + +Miden leverages delegated proving for a few technical and practical reasons: + +1. **Computational:** Generating zero-knowledge proofs is a computationally intensive work. The proving process requires significant processing power and memory, making it impractical for some end-user devices (like smartphones) to generate. +2. **Technical architecture**: +Miden's architecture separates concerns between: + - **Transaction Creation**: End users create and sign transactions + - **Proof Generation**: Specialized provers generate validity proofs + - **Verification**: The network verifies these proofs +3. **Proving efficiency**: +Delegated provers can use optimized hardware that wouldn't be available to end-user devices, specifically designed for the mathematical operations needed in STARK proof generation. + +## What is the lifecycle of a transaction? + +### 1. Transaction Creation + +- User creates a transaction specifying the operations to perform (transfers, contract interactions, etc.) +- Client performs preliminary validation of the transaction and its structure +- The user authorizes the specified state transitions by signing the transaction + +### 2. Transaction Submission + +- The signed transaction is submitted to Miden network nodes +- The transaction enters the mempool (transaction pool) where it waits to be selected to be included in the state +- Nodes perform basic validation checks on the transaction structure and signature + +### 3. Transaction Selection + +- A sequencer (or multiple sequencers in a decentralized setting) selects transactions from the mempool +- The sequencer groups transactions into bundles based on state access patterns and other criteria +- The transaction execution order is determined according to protocol mechanism + +### 4. Transaction Execution + +- The current state relevant to the transaction is loaded +- The Miden VM executes the transaction operations +- **State Transition Computation**: The resulting state transitions are computed +- An execution trace of the transaction is generated which captures all the computation + +### 5. Proof Generation + +- A STARK based cryptographic proof is generated attesting to the correctness of the execution +- A proof for the aggregated transaction is created + +### 6. Block Production + +- The aggregated bundle of transactions along with their proofs are assembled into a block +- A recursive proof attesting to all bundle proofs is generated +- The block data structure is finalized with the aggregated proof + +### 7. L1 Submission + +- Transaction data is posted to the data availability layer +- The block proof and state delta commitment are submitted to the Miden contract (that is bridged to Ethereum/Agglayer) +- The L1 contract verifies validity of the proof +- Upon successful verification, the L1 contract updates the state root + +### 8. Finalization + +- Transaction receipts and events are generated +- The global state commitment is updated to reflect the new state +- The transaction is now considered finalized on the L1 +- Users and indexers get notified/updated about the transaction completion + +## Do notes in Miden support recency conditions? + +Yes, Miden enables consumption of notes based on time conditions, such as: + +- A specific block height being reached +- A timestamp threshold being passed +- An oracle providing specific data +- Another transaction being confirmed + +## What does a Miden operator do in Miden? + +A Miden operator is an entity that maintains the infrastructure necessary for the functioning of the Miden rollup. Their roles may involve: + +1. Running Sequencer Nodes +2. Operating the Prover Infrastructure +3. Submitting Proofs to L1 +4. Maintaining Data Availability +5. Participating in the Consensus Mechanism + +## How does bridging works in Miden? + +Miden does not yet have a fully operational bridge, work in progress. + +## What does the gas fee model of Miden look like? + +Miden does not yet have a fully implemented fee model, work in progress. diff --git a/versioned_docs/version-0.12 (stable)/glossary.md b/versioned_docs/version-0.12 (stable)/glossary.md new file mode 100644 index 0000000..923aca9 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/glossary.md @@ -0,0 +1,89 @@ +# Glossary + +## Account + +An account is a data structure that represents an entity (user account, smart contract) of the Miden blockchain, they are analogous to smart contracts. + +## Account builder + +Account builder provides a structured way to create and initialize new accounts on the Miden network with specific properties, permissions, and initial state. + +## AccountCode + +Represents the executable code associated with an account. + +## AccountComponent + +An AccountComponent can be described as a modular unit of code to represent the functionality of a Miden Account. Each AccountCode is composed of multiple AccountComponent's. + +## AccountId + +The AccountId is a value that uniquely identifies each account in Miden. + +## AccountIdVersion + +The AccountIdVersion represents the different versions of account identifier formats supported by Miden. + +## AccountStorage + +The AccountStorage is a key-value store associated with an account. It is made up of storage slots. + +## Asset + +An Asset represents a digital resource with value that can be owned, transferred, and managed within the Miden blockchain. + +## AssetVault + +The AssetVault is used for managing assets within accounts. It provides a way for storing and transfering assets associated with each account. + +## Batch + +A Batch allows multiple transactions to be grouped together, these batches will then be aggregated into blocks, improving network throughput. + +## Block + +A Block is a fundamental data structure which groups multiple batches together and forms the blockchain's state. + +## Delta + +A Delta represents the changes between two states `s` and `s'`. Applying a Delta `d` to `s` would result in `s'`. + +## Felt + +A Felt or Field Element is a data type used for cryptographic operations. It represents an element in the finite field used in Miden. + +## Kernel + +A fundamental module of the MidenVM that acts as a base layer by providing core functionality and security guarantees for the protocol. + +## Miden Assembly + +An assembly language specifically designed for the Miden VM. It's a low-level programming language with specialized instructions optimized for zero-knowledge proof generation. + +## Note + +A Note is a fundamental data structure that represents an offchain asset or a piece of information that can be transferred between accounts. Miden's UTXO-like (Unspent Transaction Output) model is designed around the concept of notes. There are output notes which are new notes created by the transaction and input notes which are consumed (spent) by the transaction. + +## Note script + +A Note script is a program that defines the rules and conditions under which a note can be consumed. + +## Note tag + +A Note tag is an identifier or metadata associated with notes that provide additional filtering capabilities. + +## Note ID + +Note ID is a unique identifier assigned to each note to distinguish it from other notes. + +## Nullifier + +A nullifier is a cryptographic commitment that marks a note as spent, preventing it from being consumed again. + +## Prover + +A Prover is responsible for generating zero-knowledge proofs that attest to the correctness of the execution of a program without revealing the underlying data. + +## Word + +A Word is a data structure that represents the basic unit of computation and storage in Miden, it is composed or four Felt's. diff --git a/versioned_docs/version-0.12 (stable)/intro.md b/versioned_docs/version-0.12 (stable)/intro.md new file mode 100644 index 0000000..4b9ce0f --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/intro.md @@ -0,0 +1,120 @@ +--- +sidebar_position: 1 +--- + +# Introduction + +![Miden Docs Background](/img/docs-background.png) + +:::note +Welcome to the Miden docs – your one-stop shop for all things Miden. +::: + +Miden is a rollup for high-throughput, private applications. + +Using Miden, builders can create novel, high-throughput, private applications for payments, DeFi, asset management, and more. Applications and users are secured by Ethereum and Agglayer. + +If you want to join the technical discussion, please check out the following: + +- [Telegram](https://t.me/BuildOnMiden) +- [Miden GitHub](https://github.com/0xMiden) +- [Roadmap](https://miden.xyz/roadmap) + +:::warning +- These docs are a work in progress. +- Some topics have been discussed in greater depth, while others require additional clarification. +::: + +## Status and features + +Miden is currently at release v0.13 – approaching mainnet readiness, with parts of the protocol still being refined and new zk-rollup features under active development for the 2026 launch. + +:::warning +Breaking changes may still occur in some components. +::: + +### Feature highlights + +#### Private accounts + +The Miden operator only tracks a commitment to account data in the public database. Users can execute smart contracts only when they know the interface and the state. + +#### Private notes + +Like private accounts, the Miden operator only tracks a commitment to notes in the public database. Users need to communicate note details to each other offchain (via a side-channel) in order to consume private notes in transactions. + +#### Public accounts + +Miden supports public smart contracts like Ethereum. The code and state of those accounts are visible to the network and anyone can execute transactions against them. + +#### Public notes + +With public notes, users are able to store all note details onchain, thus eliminating the need to communicate note details via side-channels. + +#### Local transaction execution + +The Miden client supports local transaction execution and proof generation. The Miden operator verifies the proof and, if valid, updates the state database accordingly. + +#### Delegated proving + +The Miden client supports delegated proving, allowing users to offload proof generation to an external service when using low-powered devices. + +#### Standardized smart contracts + +Currently, there are three different standardized smart contracts available. A basic wallet contract for sending and receiving assets, plus fungible and non-fungible faucets to mint and burn assets. + +All accounts are written in [MASM](https://0xmiden.github.io/miden-vm/user_docs/assembly/main.html). + +#### Customized smart contracts + +Accounts can expose any interface by combining custom account components. Account components can be simple smart contracts, like the basic wallet, or they can be entirely custom-made and reflect any logic due to the underlying Turing-complete Miden VM. + +#### P2ID, P2IDR, and SWAP note scripts + +Currently, there are three different standardized note scripts available. Two different versions of pay-to-id scripts of which P2IDR is reclaimable, and a swap script that allows for simple token swaps. + +#### Customized note scripts + +Users are also able to write their own note scripts. Note scripts are executed when notes are consumed, and can express arbitrary logic thanks to the underlying Turing-complete Miden VM. + +#### Simple block building + +The Miden operator running the Miden node builds the blocks containing transactions. + +#### Maintaining state + +The Miden node stores all necessary information in its state databases and provides this information via its RPC endpoints. + +### Work-In-Progress features + +:::warning +The following features are currently under active development. +::: + +#### Network transactions + +Transaction execution and proving can be outsourced to the network and to the Miden operator. These transactions will be required for public shared state, and they can be useful if the user's device is not powerful enough to prove transactions efficiently. + +#### Rust compiler + +In order to write account, note, or transaction scripts in Rust, there will be a compiler from Rust to Miden Assembly. + +#### Block and epoch proofs + +The Miden node will recursively verify transactions and, in doing so, build batches of transactions, blocks, and epochs. + +## Benefits of Miden + +- Ethereum security. +- Developers can build applications that are infeasible on other systems. For example: + - **onchain order book exchange** due to parallel transaction execution and updatable transactions. + - **complex, incomplete information mechanisms** due to client-side proving and cheap complex computations. + - **safe wallets** due to hidden account state. +- Stronger privacy than on Ethereum or Solana – starting with Web2-level confidentiality, and evolving toward full self-sovereignty. +- Transactions can be recalled and updated. +- Lower fees due to client-side proving. +- dApps on Miden are safe to use due to account abstraction and compile-time safe Rust smart contracts. + +## License + +Licensed under the [MIT License](http://opensource.org/licenses/MIT). diff --git a/versioned_docs/version-0.12 (stable)/miden-base/_category_.yml b/versioned_docs/version-0.12 (stable)/miden-base/_category_.yml new file mode 100644 index 0000000..6a45f56 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-base/_category_.yml @@ -0,0 +1,4 @@ +label: "Protocol" +# Determines where this documentation section appears relative to other sections on the main documentation page (which is the parent of this folder in the miden-docs repository) +position: 3 +collapsed: true diff --git a/versioned_docs/version-0.12 (stable)/miden-base/account/_category_.yml b/versioned_docs/version-0.12 (stable)/miden-base/account/_category_.yml new file mode 100644 index 0000000..8383b5d --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-base/account/_category_.yml @@ -0,0 +1,4 @@ +label: "Accounts" +# Determines where this documentation section appears relative to other sections in the parent folder +position: 2 +collapsed: true diff --git a/versioned_docs/version-0.12 (stable)/miden-base/account/address.md b/versioned_docs/version-0.12 (stable)/miden-base/account/address.md new file mode 100644 index 0000000..1287ffc --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-base/account/address.md @@ -0,0 +1,123 @@ +--- +sidebar_position: 3 +--- + +# Address + +## Purpose + +An address is an extension to account IDs and other identifiers that facilitates sending and receiving of [notes](../note). It serves four main purposes explained in this section. + +### Communicating receiver information + +An address is designed for the note receiver to communicate information about themselves to the sender. + +The receiver can choose to disclose various pieces of information that control how the note itself is structured. + +Consider a few examples that use different address mechanisms: + +- The [Pay-to-ID note](../note#p2id-pay-to-id): the note itself can only be consumed if the account ID encoded in the note details matches the ID of the account that tries to consume it. To receive a P2ID note, the receiver should communicate an `AddressId::AccountId` type to the sender. +- A "Pay-to-PoW" note that can only be consumed if the receiver can provide a valid seed such that the hash of the seed results in a value with n leading zero bits. The receiver communicates an `AddressId::PoW` type to the sender, which encodes the target number of leading zero bits (and a salt to avoid re-use of the same seed).* +- A "Pay-to-Public-Key" note that stores a public (signature) key and checks if the receiver can provide a valid cryptographic signature for that key. The `AddressId::PublicKey` type must encode the public key.* + +These different address mechanisms provide different levels of privacy and security: +- `AddressId::AccountId`: the receiver is uniquely identifiable, but they are the only ones who can consume the note. +- `AddressId::PoW`: the receiver is not revealed publicly, but potentially many entities can consume the note. The receiver has an advantage by specifying the salt. +- `AddressId::PublicKey`: the receiver `AccountId` is not revealed publicly, only their public key. A fresh `AddressId::PublicKey` can be used for receiving each note, resulting in increased privacy. + +:::note +The "Pay-to-PoW" and "Pay-to-Public-Key" notes and the corresponding address types are for illustration purposes only. They are not part of the Miden library. +::: + +### Communicating channel information + +For notes which are sent privately, the sender needs to communicate the full note details to the receiver. This can be done via a side channel, such as a messenger, email, or via a QR code. We would like to avoid the necessity of operating two-way communication channels for each note. Rather, we operate under the assumption that once the receiver shares their `Address` (directly with the sender, or via a bulletin board, i.e. a one-way channel), they don't need to stay online and wait for the sender to send back the full note details. + +Instead, our Miden client connects to a _Note Transport Layer_, which stores encrypted note details together with the associated public metadata for each note. The receiver can query the Note Transport Layer for `NoteTag`s they are interested in. Typically, a `NoteTag` encodes a few leading bits (14 by default) of the receiver's `AccountId`. Querying the Note Transport Layer for 14-bit `NoteTag`s reduces the receiver's privacy, but at the same time allows them to perform less work downloading and trial-decrypting the notes than if fewer bits were encoded. + +With an `Address`, e.g. the [`AddressId::AccountId`](./address#addressaccountid) variant, the receiver could specify how many bits of their `AccountId` they want to disclose to the sender and thus choose their level of privacy. + +### Account interface discovery + +An address allows the sender of the note to easily discover the interface of the receiving account. As explained in the [account interface](./code#interface) section, every account can have a different set of procedures that note scripts can call, which is the _interface_ of the account. In order for the sender of a note to create a note that the receiver can consume, the sender needs to know the interface of the receiving account. This can be communicated via the address, which encodes a mapping of standard interfaces like the basic wallet. + +If a sender wants to create a note, it is up to them to check whether the receiver account has an interface that it compatible with that note. The notion of an address doesn't exist at protocol level and so it is up to wallets or clients to implement this interface compatibility check. + +### Note encryption + +An address can include a public encryption key that enables senders to securely encrypt note payloads for the receiver. This ensures that only the intended recipient, who holds the corresponding private key, can decrypt and read the note contents. + +## Structure + +An address consists of two parts: +- An identifier that determines what the address fundamentally points to, e.g. an account ID or, in the future, a public key. +- Routing parameters, that customize how a sender creates notes for the receiver, or in other words, how they are routed. + +The separation between these two parts is represented by an underscore (`_`) in the encoded address: + +```text +mm1arp0azyk9jugtgpnnhle8daav58nczzr_qpgqqwcfx0p + | | + account ID routing parameters +``` + +### Relationship to Identifiers + +The routing parameters in an address can encode exactly one account interface, which is a deliberate limitation to keep the size of addresses small. Users can generate multiple addresses for the same identifier like account ID or public key, in order to communicate different interfaces to senders. In other words, there could be multiple different addresses that point to the same account, each encoding a different interface. So, the relationship from addresses to their underlying identifiers is n-to-1. + +As an example, these addresses contain the same account ID but different routing parameters: + +```text +mm1arp0azyk9jugtgpnnhle8daav58nczzr_qpgqqwcfx0p +mm1arp0azyk9jugtgpnnhle8daav58nczzr_qzsqqd4avz7 +mm1arp0azyk9jugtgpnnhle8daav58nczzr_qruqqqgqjmsgjsh3687mt2w0qtqunxt3th442j48qwdnezl0fv6qm3x9c8zqsv7pku +``` + +The third example above includes an encryption key in the routing parameters, which results in a longer encoded address string. + +### Address Types + +The supported **address types** are: +- `AddressId::AccountId` (type `232`): An address pointing to an account ID. + - Choosing `232` as the type byte means that all addresses that encode an account ID start with `mm1a`, where `a` conveniently indicates "account". + +:::note +Adding a public key-based address type is planned. +::: + +### Routing Parameters + +The supported routing parameters are detailed in this section. + +#### Address Interface + +The address interface informs the sender of the capabilities of the [receiver account's interface](./code#interface). + +The supported **address interfaces** are: +- `BasicWallet` (type `0`): The standard basic wallet interface. See the [account code](./code#interface) docs for details. + +#### Note Tag Length + +The note tag length routing parameter allows specifying the length of the [note tag](../note#note-discovery) that the sender should create. This parameter determines how many bits of the account ID are encoded into note tags of notes targeted to this address. This lets the owner of the account choose their level of privacy. A higher tag length makes the address ID more uniquely identifiable and reduces privacy, while a shorter length increases privacy at the cost of matching more notes published onchain. + +#### Encryption Key + +The encryption key routing parameter enables secure note payload encryption by allowing the receiver to provide a public encryption key in their address. When present, senders can use this key to encrypt the note payload using sealed box encryption, ensuring that only the receiver can decrypt and read the note contents. + +The supported **encryption schemes** are: +- `X25519_XChaCha20Poly1305`: Curve25519-based key exchange with XChaCha20-Poly1305 authenticated encryption +- `K256_XChaCha20Poly1305`: secp256k1-based key exchange with XChaCha20-Poly1305 authenticated encryption +- `X25519_AeadRpo`: Curve25519-based key exchange with RPO-based authenticated encryption +- `K256_AeadRpo`: secp256k1-based key exchange with RPO-based authenticated encryption + +The encryption key is optional in an address. If not provided, senders may use alternative encryption mechanisms or send unencrypted notes. + +When an encryption key is included in the address, it is encoded in bech32 format alongside other routing parameters. The encoding consists of a 1-byte variant discriminant followed by the public key bytes (32 bytes for Curve25519 keys, 33 bytes for secp256k1 keys in their compressed format). + +## Encoding + +The two parts of an address are encoded as follows: +- The identifier is encoded in [**bech32**](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki). See the [account ID encoding](id.md#encoding) section for details. +- The routing parameters are encoded in bech32 as well, but without the HRP or `1` separator. + - This means the routing parameter string's alphabet is consistent with that of the address ID. + - It also means the routing parameters have their own checksum, which is important so address ID and routing parameters can be separated at any time without causing validation issues. diff --git a/versioned_docs/version-0.12 (stable)/miden-base/account/code.md b/versioned_docs/version-0.12 (stable)/miden-base/account/code.md new file mode 100644 index 0000000..2f5eedf --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-base/account/code.md @@ -0,0 +1,40 @@ +--- +sidebar_position: 4 +title: "Code" +--- + +# Account Code + +:::note +A collection of procedures defining the `Account`'s programmable interface. +::: + +Every Miden `Account` is essentially a smart contract. The `Code` defines the account's procedures, which can be invoked through both [note scripts](../note#script) and [transaction scripts](../transaction#inputs). Key characteristics include: + +- **Mutable access:** Only the `Account`'s own procedures can modify its storage and vault. All state changes — such as updating storage slots or transferring assets — must occur through these procedures. +- **Function commitment:** Each function can be called by its [MAST](https://0xMiden.github.io/miden-vm/user_docs/assembly/main.html) root. The root represents the underlying code tree as a 32-byte commitment. This ensures integrity which means a function's behavior cannot change without changing the MAST root. +- **Asset creation:** Faucet `Accounts` can create assets. + +## Interface + +An account's code is typically the result of merging multiple [account components](./components). This results in a set of procedures that make up the _interface_ of the account. As an example, a typical wallet uses the so-called _basic wallet_ interface, which is defined in `miden::contracts::wallets::basic`. It consists of the `receive_asset` and `move_asset_to_note` procedures. If an account has this interface, i.e. this set of procedures, it can consume standard [P2ID notes](../note#p2id-pay-to-id). If it doesn't, it can't consume this type of note. So, adhering to standard interfaces such as the basic wallet will generally make an account more interoperable. + +## Authentication + +Authenticating a transaction, and therefore the changes to the account, is done with an _authentication procedure_. Every account's code must provide exactly one authentication procedure. It is automatically called during the transaction epilogue, i.e. after all note scripts and the transaction script have been executed. + +Such an authentication procedure typically inspects the transaction and then decides whether a signature is required to authenticate the changes. It does this by: + +- checking which account procedures have been called + - Example: Authentication is required if the `distribute` procedure was called but not if `burn` was called. +- inspecting the account delta. + - Example: Authentication is required if a cryptographic key in storage was updated. + - Example: Authentication is required if an asset was removed from the vault. +- checking whether notes have been consumed. +- checking whether notes have been created. + +Recall that an [account's nonce](index.md#nonce) must be incremented whenever its state changes. Only authentication procedures are allowed to do so, to prevent accidental or unintended authorization of state changes. + +### Procedure tracking + +The authentication procedure can base its authentication decision on whether a specific account procedure was called during the transaction (procedure tracking). A procedure is considered "tracked" only if it invokes account-restricted kernel APIs (procedures that are only allowed to be called from the account context, e.g. `exec.faucet::mint`). Procedures that execute only local instructions (e.g., a noop `push.0 drop`) will not be marked as tracked. diff --git a/versioned_docs/version-0.12 (stable)/miden-base/account/components.md b/versioned_docs/version-0.12 (stable)/miden-base/account/components.md new file mode 100644 index 0000000..b304910 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-base/account/components.md @@ -0,0 +1,194 @@ +--- +sidebar_position: 6 +title: "Components" +--- + +# Account Components + +Account components are reusable units of functionality that define a part of an account's code and storage. Multiple account components can be merged together to form an account's final [code](./code) and [storage](./storage). + +As an example, consider a typical wallet account, capable of holding a user's assets and requiring authentication whenever assets are added or removed. Such an account can be created by merging a `BasicWallet` component with an `RpoFalcon512` authentication component. The basic wallet does not need any storage, but contains the code to move assets in and out of the account vault. The authentication component holds a user's public key in storage and additionally contains the code to verify a signature against that public key. Together, these components form a fully functional wallet account. + +## Account Component templates + +An account component template is a description of an account component and contains all the +information needed to initialize it. + +Specifically, a template specifies a component's **metadata** and its **code**. + +Once defined, a component template can be instantiated to an account component, which can then be +merged to form the account's `Code` and `Storage`. + +## Component code + +The template's code defines a library of functions that can read and write to the storage slots of the component. + +## Component metadata + +The component metadata describes the account component entirely: its name, description, version, and storage layout. + +The storage layout must specify a contiguous list of slot values that starts at index `0`, and can optionally specify initial values for each of the slots. Alternatively, placeholders can be utilized to identify values that should be provided at the moment of instantiation. + +### TOML specification + +The component metadata can be defined using TOML. Below is an example specification: + +```toml +name = "Fungible Faucet" +description = "This component showcases the component template format, and the different ways of +providing valid values to it." +version = "1.0.0" +supported-types = ["FungibleFaucet"] + +[[storage]] +name = "token_metadata" +description = "Contains metadata about the token associated to the faucet account. The metadata +is formed by three fields: max supply, the token symbol and the asset's decimals" +slot = 0 +value = [ + { type = "felt", name = "max_supply", description = "Maximum supply of the token in base units" }, + { type = "token_symbol", value = "TST" }, + { type = "u8", name = "decimals", description = "Number of decimal places for converting to absolute units", value = "10" }, + { value = "0x0" } +] + +[[storage]] +name = "owner_public_key" +description = "This is a value placeholder that will be interpreted as a Falcon public key" +slot = 1 +type = "auth::rpo_falcon512::pub_key" + +[[storage]] +name = "map_storage_entry" +slot = 2 +values = [ + { key = "0x1", value = ["0x0", "249381274", "998123581", "124991023478"] }, + { + key = "0xDE0B1140012A9FD912F18AD9EC85E40F4CB697AE", + value = { + name = "value_placeholder", + description = "This value will be defined at the moment of instantiation" + } + } +] + +[[storage]] +name = "procedure_thresholds" +description = "Map which stores procedure thresholds (PROC_ROOT -> signature threshold)" +slot = 3 +type = "map" + +[[storage]] +name = "multislot_entry" +slots = [4,5] +values = [ + ["0x1","0x2","0x3","0x4"], + ["50000","60000","70000","80000"] +] +``` + +#### Specifying values and their types + +In the TOML format, any value that is one word long can be written as a single value, or as exactly four field elements. In turn, a field element is a number within Miden's finite field. + +A word can be written as a hexadecimal value, and field elements can be written either as hexadecimal or decimal numbers. In all cases, numbers should be input as strings. + +In our example, the `token_metadata` single-slot entry is defined as four elements, where the first element is a placeholder, and the second, third and fourth are hardcoded values. + +##### Word types + +Valid word types are `word` (default type) and `auth::rpo_falcon512::pub_key` (represents a Falcon public key). Both can be written and interpreted as hexadecimal strings. + +##### Felt types + +Valid field element types are `u8`, `u16`, `u32`, `felt` (default type) and `token_symbol`: + +- `u8`, `u16` and `u32` values can be parsed as decimal numbers and represent 8-bit, 16-bit and 32-bit unsigned integers +- `felt` values represent a field element, and can be parsed as decimal or hexadecimal values +- `token_symbol` values represent the symbol for basic fungible tokens, and are parsed as strings made of four uppercase characters + +#### Header + +The metadata header specifies four fields: + +- `name`: The component template's name +- `description` (optional): A brief description of the component template and its functionality +- `version`: A semantic version of this component template +- `supported-types`: Specifies the types of accounts on which the component can be used. Valid values are `FungibleFaucet`, `NonFungibleFaucet`, `RegularAccountUpdatableCode` and `RegularAccountImmutableCode` + +#### Storage entries + +An account component template can have multiple storage entries. A storage entry can specify either a **single-slot value**, a **multi-slot value**, or a **storage map**. + +Each of these storage entries contain the following fields: + +- `name`: A name for identifying the storage entry +- `description` (optional): Describes the intended function of the storage slot within the component definition + +Additionally, based on the type of the storage entry, there are specific fields that should be specified. + +##### Single-slot value + +A single-slot value fits within one slot (i.e., one word). + +For a single-slot entry, the following fields are expected: + +- `slot`: Specifies the slot index in which the value will be placed +- `value` (optional): Contains the initial storage value for this slot. Will be interpreted as a `word` unless another `type` is specified +- `type` (optional): Describes the expected type for the slot + +If no `value` is provided, the entry acts as a placeholder, requiring a value to be passed at instantiation. In this case, specifying a `type` is mandatory to ensure the input is correctly parsed. So the rule is that at least one of `value` and `type` has to be specified. +Valid types for a single-slot value are `word` or `auth::rpo_falcon512::pub_key`. + +In the above example, the first and second storage entries are single-slot values. + +##### Storage map entries + +[Storage maps](./storage#map-slots) consist of key-value pairs, where both keys and values are single words. + +Storage map entries can specify the following fields: + +- `slot`: Specifies the slot index in which the root of the map will be placed +- `values` (optional): Contains a list of map entries, defined by a `key` and `value`. Each entry is + interpreted as a word, and keys or values may themselves be expressed via placeholders. +- `type = "map"` (optional): When provided without `values`, the entry is treated as a templated map + whose contents must be provided at instantiation time through [`InitStorageData`](#initializing-placeholder-values). + If `values` are present, the entry is interpreted as a static map regardless of the `type` field, so + specifying `type = "map"` becomes purely descriptive in that case. + +In the example, the third storage entry defines a static storage map with two initial entries, while +the fourth entry (`procedure_thresholds`) is a templated map whose contents are supplied at +instantiation time. + +##### Multi-slot value + +Multi-slot values are composite values that exceed the size of a single slot (i.e., more than one `word`). + +For multi-slot values, the following fields are expected: + +- `slots`: Specifies the list of contiguous slots that the value comprises +- `values`: Contains the initial storage value for the specified slots + +Placeholders can currently not be defined for multi-slot values. In our example, the fifth entry defines a two-slot value. + +#### Initializing placeholder values + +When a storage entry introduces placeholders, an implementation must provide their concrete values +at instantiation time. This is done through `InitStorageData` (available as `miden_objects::account::InitStorageData`), which can be created programmatically or loaded from TOML using `InitStorageData::from_toml()`. + +For example, the templated map entry above can be populated from TOML as follows: + +```toml +procedure_thresholds = [ + { + key = "0xd2d1b6229d7cfb9f2ada31c5cb61453cf464f91828e124437c708eec55b9cd07", + value = "0x00000000000000000000000000000000000000000000000000000000000001" + }, + { + key = "0x2217cd9963f742fc2d131d86df08f8a2766ed17b73f1519b8d3143ad1c71d32d", + value = ["0", "0", "0", "2"] + } +] +``` + +Each element in the array is a fully specified key/value pair. Keys and values can be written either as hexadecimal words or as an array of four field elements (decimal or hexadecimal strings). This syntax complements the existing `values = [...]` form used for static maps, and mirrors how map entries are provided in component metadata. diff --git a/versioned_docs/version-0.12 (stable)/miden-base/account/id.md b/versioned_docs/version-0.12 (stable)/miden-base/account/id.md new file mode 100644 index 0000000..ee8362b --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-base/account/id.md @@ -0,0 +1,72 @@ +--- +sidebar_position: 2 +title: "ID" +--- + +# Account ID + +:::note +An immutable and unique identifier for the `Account`. +::: + +The `Account` ID is a 120-bit long number. This identifier is designed to contain the metadata of an account. The metadata includes the [account type](#account-type), [account storage mode](#account-storage-mode) and the version of the `Account`. This metadata is included in the ID to ensure it can be read without needing the full account state. + +The ID is generated by hashing a randomly generated seed together with commitments to the initial code and storage of the `Account` until the resulting ID has the desired account type and storage mode. This process requires a small amount of Proof-of-Work (9 bits) that can be done even by low-powered devices. The resulting 256-bit hash is shortened to 120 bits. + +Account type and storage mode are chosen at account creation time and cannot be changed later. + +### Account type + +There are two main categories of accounts in Miden: **basic accounts** and **faucets**. + +- **Basic Accounts:** + Basic Accounts may be either mutable or immutable: + + - _Mutable:_ Code can be changed after deployment. + - _Immutable:_ Code cannot be changed once deployed. + +- **Faucets:** + Faucets are always immutable and can be specialized by the type of assets they issue: + - _Fungible Faucet:_ Can issue fungible [assets](../asset). + - _Non-fungible Faucet:_ Can issue non-fungible [assets](../asset). + +### Account storage mode + +Users can choose whether their accounts are stored publicly or privately. The preference is encoded in the third and fourth most significant bits of the account's ID: + +- **Public Accounts:** + The account's state is stored on-chain, similar to how accounts are stored in public blockchains like Ethereum. + +- **Network `Account`s:** + The account's state is stored on-chain, just like **public** accounts. Additionally, the network will monitor this account for any public notes targeted at it and attempt to create network transactions against the account, which consume the notes. Contracts that rely on a shared, publicly accessible state (e.g., an AMM) should be network accounts. + +- **Private Accounts:** + Only a commitment (hash) to the account's state is stored on-chain. This mode is suitable for users who prioritize privacy or plan to store a large amount of data in their `Account`. To interact with a private `Account`, a user must have knowledge of its interface. + +## Encoding + +:::info +Bech32 is the preferred encoding format and should be used for user-facing applications like wallets or websites. +::: + +An `Account` ID can be encoded in different formats: + +1. [**Bech32**](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki) (user-facing): + - Example: `mm1apk5f8jqxnadegr46xtklmm78qhdgkwc` + - **Benefits**: + - Built-in error detection via checksum algorithm + - Human-readable prefix indicates network ID + - Less prone to transcription errors + - **Structure**: + - [Human-readable prefix](https://github.com/satoshilabs/slips/blob/master/slip-0173.md) that + determines the network: + - `mm` (indicates **M**iden **M**ainnet) + - `mtst` (indicates Miden Testnet) + - `mdev` (indicates Miden Devnet) + - Separator: `1` + - Data part with integrated checksum + +2. **Hexadecimal**: + - Example: `0xd7585ada5ab5d2b01c77fad88c0ae4` + - Frequently used encoding for blockchain addresses + - Used to identify accounts in command-line interfaces or explorers. diff --git a/versioned_docs/version-0.12 (stable)/miden-base/account/index.md b/versioned_docs/version-0.12 (stable)/miden-base/account/index.md new file mode 100644 index 0000000..e43aaee --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-base/account/index.md @@ -0,0 +1,59 @@ +--- +sidebar_position: 1 +--- + +# Accounts / Smart Contracts + +An `Account` represents the primary entity in Miden. It is capable of holding assets, storing data, and executing custom code. Each `Account` is a smart contract with a programmable interface through which note and transaction scripts can interact with the account's state and assets. By executing [transactions](../transaction) against an account, its state can be modified. + +## What is the purpose of an account? + +In Miden's hybrid UTXO- and account-based model, accounts enable the creation of expressive smart contracts via a Turing-complete language. For example, an account with a wallet interface can hold the assets of a user while a DEX account could have an interface to trade assets. + +## Account composition + +An `Account` is composed of several core parts, illustrated below: + +

+ Account diagram +

+ +These parts are: + +1. [ID](id.md) +2. [Code](code.md) +3. [Storage](storage.md) +4. [Vault](#vault) +5. [Nonce](#nonce) + +### Vault + +:::note +A collection of [assets](../asset.md) stored by the `Account`. +::: + +Large amounts of fungible and non-fungible assets can be stored in the account's vault. + +### Nonce + +:::note +A counter incremented with each state update to the `Account`. +::: + +The nonce ensures that an account has a unique _commitment_ (or "hash") after every transaction, even if it contains the same assets and has the same storage state. That in turn allows ordering of transactions and prevents replay attacks. Whenever the state of an account changes in a transaction, its nonce must be incremented and it can only be incremented exactly once per transaction. + +Note that a transaction does not always change the state of an account. For instance, a transaction in which two SWAP notes are matched together does not necessarily change anything about the account state. Consequently, the nonce does not have to be incremented. + +## Account creation + +For an `Account` to be recognized by the network, it must exist in the [account database](../state#account-database) maintained by Miden node(s). + +However, a user can locally create a new `Account` ID before it's recognized network-wide. The typical process might be: + +1. Alice generates a new `Account` ID locally (according to the desired `Account` type) using the Miden client. +2. The Miden client checks with a Miden node to ensure the ID does not already exist. +3. Alice shares the new ID with Bob to receive an asset. +4. Bob executes a transaction against his account, creating a note with assets for Alice. +5. Alice consumes Bob's note in a transaction against her new account to claim the asset. This + transaction is the first transaction against Alice's account and so it will register the account + ID in the account database. diff --git a/versioned_docs/version-0.12 (stable)/miden-base/account/storage.md b/versioned_docs/version-0.12 (stable)/miden-base/account/storage.md new file mode 100644 index 0000000..06146ef --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-base/account/storage.md @@ -0,0 +1,33 @@ +--- +sidebar_position: 5 +title: "Storage" +--- + +# Account Storage + +:::note +A flexible, arbitrary data store within the `Account`. +::: + +The [storage](https://docs.rs/miden-objects/latest/miden_objects/account/struct.AccountStorage.html) is divided into a maximum of 255 indexed [storage slots](https://docs.rs/miden-objects/latest/miden_objects/account/enum.StorageSlot.html). Each slot can either store a 32-byte value or serve as the cryptographic root to a key-value store with the capacity to store large amounts of data. + +- **Value slots:** Contains 32 bytes of arbitrary data. +- **Map slots:** Contains a [StorageMap](#map-slots), a key-value store where both keys and values are 32 bytes. The slot's value is a commitment to the entire map. + +An account's storage is typically the result of merging multiple [account components](./components). + +## Value Slots + +A value slot can be used whenever 32 bytes of data is enough, e.g. for storing a single public key for use in [authentication procedures](code#authentication). + +## Map Slots + +A map slot contains a `StorageMap` which is a key-value store implemented as a sparse Merkle tree (SMT). This allows an account to store a much larger amount of data than would be possible using only the account's storage slots. The root of the underlying SMT is stored in a single account storage slot, and each map entry is a leaf in the tree. When retrieving an entry (e.g., via `active_account::get_map_item`), its inclusion is proven using a Merkle proof. + +Key properties of `StorageMap`: + +- **Efficient, scalable storage:** The SMT structure enables efficient storage and proof of inclusion for a large number of entries, while only storing the root in the account's storage slot. +- **Partial presence:** Not all entries of the map need to be present at transaction execution time to access or modify the map. It is sufficient if only the accessed or modified items are present in the advice provider. +- **Key hashing:** Since map keys are user-chosen and may not be uniformly distributed, keys are hashed before being inserted into the SMT. This ensures a more balanced tree and mitigates efficiency issues due to key clustering. The original keys are retained in a separate map, allowing for introspection (e.g., querying the set of stored original keys for debugging or explorer scenarios). This introduces some redundancy, but enables useful features such as listing all stored keys. + +This design allows for flexible, scalable, and privacy-preserving storage within accounts, supporting both large datasets and efficient proof generation. diff --git a/versioned_docs/version-0.12 (stable)/miden-base/asset.md b/versioned_docs/version-0.12 (stable)/miden-base/asset.md new file mode 100644 index 0000000..ce0e15e --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-base/asset.md @@ -0,0 +1,73 @@ +--- +sidebar_position: 4 +--- + +# Assets + +An `Asset` is a unit of value that can be transferred from one [account](./account) to another using [notes](note). + +## What is the purpose of an asset? + +In Miden, assets serve as the primary means of expressing and transferring value between [accounts](./account) through [notes](note). They are designed with four key principles in mind: + +1. **Parallelizable exchange:** + By managing ownership and transfers directly at the account level instead of relying on global structures like ERC20 contracts, accounts can exchange assets concurrently, boosting scalability and efficiency. + +2. **Self-sovereign ownership:** + Assets are stored in the accounts directly. This ensures that users retain complete control over their assets. + +3. **Censorship resistance:** + Users can transact freely and privately with no single contract or entity controlling `Asset` transfers. This reduces the risk of censored transactions, resulting in a more open and resilient system. + +4. **Fee payment in native asset:** + Transaction fees are paid in the chain's native asset as defined by the current reference block's fee parameters. See [Fees](fees.md). + +## Native asset + +:::note +All data structures following the Miden asset model that can be exchanged. +::: + +Native assets adhere to the Miden `Asset` model (encoding, issuance, storage). Every native `Asset` is encoded using 32 bytes, including both the [ID](./account/id) of the issuing account and the `Asset` details. + +### Issuance + +:::note +Only [faucet](./account/id#account-type) accounts can issue assets. +::: + +Faucets can issue either fungible or non-fungible assets as defined at account creation. The faucet's code specifies the `Asset` minting conditions: i.e., how, when, and by whom these assets can be minted. Once minted, they can be transferred to other accounts using notes. + +

+ Asset issuance +

+ +### Type + +#### Fungible asset + +Fungible assets are encoded with the amount and the `faucet_id` of the issuing faucet. The amount is always $2^{63}-1$ or smaller, representing the maximum supply for any fungible `Asset`. Examples include ETH and various stablecoins (e.g., DAI, USDT, USDC). + +#### Non-fungible asset + +Non-fungible assets are encoded by hashing the `Asset` data into 32 bytes and placing the `faucet_id` as the second element. Examples include NFTs like a DevCon ticket. + +### Storage + +[Accounts](./account) and [notes](note) have vaults used to store assets. Accounts use a sparse Merkle tree as a vault while notes use a simple list. This enables an account to store a practically unlimited number of assets while a note can only store 255 assets. + +

+ Asset storage +

+ +### Burning + +Assets in Miden can be burned through various methods, such as rendering them unspendable by storing them in an unconsumable note, or sending them back to their original faucet for burning using it's dedicated function. + +## Alternative asset models + +:::note +All data structures not following the Miden asset model that can be exchanged. +::: + +Miden is flexible enough to support other `Asset` models. For example, developers can replicate Ethereum’s ERC20 pattern, where fungible `Asset` ownership is recorded in a single account. To transact, users send a note to that account, triggering updates in the global hashmap state. diff --git a/versioned_docs/version-0.12 (stable)/miden-base/blockchain.md b/versioned_docs/version-0.12 (stable)/miden-base/blockchain.md new file mode 100644 index 0000000..e6b6168 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-base/blockchain.md @@ -0,0 +1,96 @@ +--- +sidebar_position: 7 +--- + +# Blockchain + +The Miden blockchain protocol describes how the [state](state) progresses through blocks, which are containers that aggregate account state changes and their proofs, together with created and consumed notes. Blocks represent the delta of the global state between two time periods, and each is accompanied by a corresponding proof that attests to the correctness of all state transitions it contains. The current global state can be derived by applying all the blocks to the genesis state. + +Miden's blockchain protocol aims for the following: + +- **Proven transactions**: All included transactions have already been proven and verified when they reach the block. +- **Fast genesis syncing**: New nodes can efficiently sync to the tip of the chain. + +

+ Execution diagram +

+ +## Batch production + +To reduce the required space on the blockchain, transaction proofs are not directly put into blocks. First, they are batched together by verifying them in the batch producer. The purpose of the batch producer is to generate a single proof that some number of proven transactions have been verified. This involves recursively verifying individual transaction proofs inside the Miden VM. As with any program that runs in the Miden VM, there is a proof of correct execution running the Miden verifier to verify transaction proofs. This results into a single batch proof. + +

+ Batch diagram +

+ +The batch producer aggregates transactions sequentially by verifying that their proofs and state transitions are correct. More specifically, the batch producer ensures: + +1. **Ordering of transactions**: If several transactions within the same batch affect a single account, the correct ordering must be enforced. For example, if `Tx1` and `Tx2` both describe state changes of account `A`, then the batch kernel must verify them in the order: `A -> Tx1 -> A' -> Tx2 -> A''`. +2. **Uniqueness of notes in a single batch**: The batch producer must ensure the uniqueness of all notes across transactions in the batch. This will prevent the situation where duplicate notes, which would share identical nullifiers, are created. Only one such duplicate note could later be consumed, as the nullifier will be marked as spent after the first consumption. It also checks for double spends in the set of consumed notes, even though the real double spent check only happens at the block production level. +3. **Expiration windows**: It is possible to set an expiration window for transactions, which in turn sets an expiration window for the entire batch. For instance, if transaction `Tx1` expires at block `8` and transaction `Tx2` expires at block `5`, then the batch expiration will be set to the minimum of all transaction expirations, which is `5`. +4. **Note erasure of erasable notes**: Erasable notes don't exist in the Notes DB. They are unauthenticated. Accounts can still consume unauthenticated notes to consume those notes faster, they don't have to wait for notes being included into a block. If creation and consumption of an erasable note happens in the same batch, the batch producer erases this note. + +## Block production + +To create a `Block`, multiple batches and their respective proofs are aggregated. `Block` production is not parallelizable and must be performed by the Miden operator. In the future, several Miden operators may compete for `Block` production. The schema used for `Block` production is similar to that in batch production—recursive verification. Multiple batch proofs are aggregated into a single `Block` proof. + +The block producer ensures: + +1. **Account DB integrity**: The Block `N+1` Account DB commitment must be authenticated against all previous and resulting account commitments across transactions, ensuring valid state transitions and preventing execution on stale states. +2. **Nullifier DB integrity**: Nullifiers of newly created notes are added to the Nullifier DB. The Block `N+1` Nullifier DB commitment must be authenticated against all new nullifiers to guarantee completeness. +3. **Block hash references**: Check that all block hashes references by batches are in the chain. +4. **Double-spend prevention**: Each consumed note’s nullifier is checked against prior consumption. The Block `N` Nullifier DB commitment is authenticated against all provided nullifiers for consumed notes, ensuring no nullifier is reused. +5. **Global note uniqueness**: All created and consumed notes must be unique across batches. +6. **Batch expiration**: The block height of the created block must be smaller then or equal to the lowest batch expiration. +7. **Block time increase**: The block timestamp must increase monotonically from the previous block. +8. **Note erasure of erasable notes**: If an erasable note is created and consumed in different batches, it is erased now. If, however, an erasable note is consumed but not created within the block, the batch it contains is rejected. The Miden operator's mempool should preemptively filter such transactions. + +In final `Block` contains: +- The commitments to the current global [state](state). +- The newly created nullifiers. +- The commitments to newly created notes. +- The new state commitments for affected private accounts. +- The full states for all affected public accounts and newly created notes. + +The `Block` proof attests to the correct state transition from the previous `Block` commitment to the next, and therefore to the change in Miden's global state. + +

+ Block diagram +

+ +:::tip + +**Block Contents:** +- **State updates**: Contains only the hashes of updated elements. For example, for each updated account, a tuple is recorded as `([account id], [new account hash])`. +- **ZK Proof**: This proof attests that, given a state commitment from the previous `Block`, a set of valid batches was executed that resulted in the new state commitment. +- The `Block` also includes the full account and note data for public accounts and notes. For example, if account `123` is a public account that has been updated, you would see a record in the **state updates** section as `(123, 0x456..)`, and the full new state of this account (which should hash to `0x456..`) would be included in a separate section. + +::: + +## Verifying blocks + +To verify that a `Block` corresponds to a valid global state transition, the following steps must be performed: + +1. Compute the hashes of public accounts and note states. +2. Ensure that these hashes match the records in the **state updates** section. +3. Verify the included `Block` proof using the following public inputs and output: + - **Input**: Previous `Block` commitment. + - **Input**: Set of batch commitments. + - **Output**: Current `Block` commitment. + +These steps can be performed by any verifier (e.g., a contract on Ethereum, Polygon AggLayer, or a decentralized network of Miden nodes). + +## Syncing from genesis + +Nodes can sync efficiently from genesis to the tip of the chain through a multi-step process: + + 1. Download historical blocks from genesis to the present. + 2. Verify zero-knowledge proofs for all blocks. + 3. Retrieve current state data (accounts, notes, and nullifiers). + 4. Validate that the downloaded state matches the latest block's state commitment. + +This approach enables fast blockchain syncing by verifying `Block` proofs rather than re-executing individual transactions, resulting in exponentially faster performance. Consequently, state sync is dominated by the time needed to download the data. + +## Consensus and decentralization + +Miden will start as a centralized L2 on the Ethereum network. Over time, Miden will decentralize, but this part of the protocol, especially consensus is not yet set. diff --git a/versioned_docs/version-0.12 (stable)/miden-base/fees.md b/versioned_docs/version-0.12 (stable)/miden-base/fees.md new file mode 100644 index 0000000..f6400c3 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-base/fees.md @@ -0,0 +1,23 @@ +--- +sidebar_position: 5.1 +--- + +# Fees + +Miden transactions pay a fee that is computed and charged automatically by the transaction kernel during the epilogue. + +## How fees are computed + +- The fee depends on the number of VM cycles the transaction executes and grows logarithmically with that count. +- The kernel estimates the number of verification cycles by taking log2 of the estimated total execution cycles (rounded up). The result is then multiplied by the `verification_base_fee` from the reference block’s fee parameters. +- In other words, the fee is proportional to the logarithm of the transaction’s number of execution cycles, scaled by the base verification fee defined in the block header. + +## Which asset is used to pay fees + +- Fees are paid in the chain’s native asset, defined by the current reference block’s fee parameters. +- The native asset is chosen once as part of the genesis block and then copied to every newly created block, which means the native asset stays consistent for a given network. + +## How fees are paid + +- Users should ensure their account’s vault holds sufficient balance of the native asset to cover the fee. The fee is charged automatically; no explicit transaction kernel API must be called. +- If the account does not contain enough of the native asset to cover the computed fee, the transaction fails during the epilogue. diff --git a/versioned_docs/version-0.12 (stable)/miden-base/img/account/account-definition.png b/versioned_docs/version-0.12 (stable)/miden-base/img/account/account-definition.png new file mode 100644 index 0000000..68907ad Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-base/img/account/account-definition.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-base/img/asset/asset-issuance.png b/versioned_docs/version-0.12 (stable)/miden-base/img/asset/asset-issuance.png new file mode 100644 index 0000000..9606ba6 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-base/img/asset/asset-issuance.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-base/img/asset/asset-storage.png b/versioned_docs/version-0.12 (stable)/miden-base/img/asset/asset-storage.png new file mode 100644 index 0000000..727bd70 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-base/img/asset/asset-storage.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-base/img/blockchain/batching.png b/versioned_docs/version-0.12 (stable)/miden-base/img/blockchain/batching.png new file mode 100644 index 0000000..c83a955 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-base/img/blockchain/batching.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-base/img/blockchain/block.png b/versioned_docs/version-0.12 (stable)/miden-base/img/blockchain/block.png new file mode 100644 index 0000000..e1f0ee8 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-base/img/blockchain/block.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-base/img/blockchain/execution.png b/versioned_docs/version-0.12 (stable)/miden-base/img/blockchain/execution.png new file mode 100644 index 0000000..74e6a48 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-base/img/blockchain/execution.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-base/img/miden-architecture-core-concepts.gif b/versioned_docs/version-0.12 (stable)/miden-base/img/miden-architecture-core-concepts.gif new file mode 100644 index 0000000..573f5df Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-base/img/miden-architecture-core-concepts.gif differ diff --git a/versioned_docs/version-0.12 (stable)/miden-base/img/miden-architecture-state-progress.gif b/versioned_docs/version-0.12 (stable)/miden-base/img/miden-architecture-state-progress.gif new file mode 100644 index 0000000..81a6d26 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-base/img/miden-architecture-state-progress.gif differ diff --git a/versioned_docs/version-0.12 (stable)/miden-base/img/network/Miden_node.png b/versioned_docs/version-0.12 (stable)/miden-base/img/network/Miden_node.png new file mode 100644 index 0000000..1271637 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-base/img/network/Miden_node.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-base/img/network/architecture-overview.png b/versioned_docs/version-0.12 (stable)/miden-base/img/network/architecture-overview.png new file mode 100644 index 0000000..842c48e Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-base/img/network/architecture-overview.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-base/img/note/note-life-cycle.png b/versioned_docs/version-0.12 (stable)/miden-base/img/note/note-life-cycle.png new file mode 100644 index 0000000..31f6135 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-base/img/note/note-life-cycle.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-base/img/note/note.png b/versioned_docs/version-0.12 (stable)/miden-base/img/note/note.png new file mode 100644 index 0000000..ed447f5 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-base/img/note/note.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-base/img/note/nullifier.png b/versioned_docs/version-0.12 (stable)/miden-base/img/note/nullifier.png new file mode 100644 index 0000000..55a6b6d Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-base/img/note/nullifier.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-base/img/state/account-db.png b/versioned_docs/version-0.12 (stable)/miden-base/img/state/account-db.png new file mode 100644 index 0000000..eb6f386 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-base/img/state/account-db.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-base/img/state/amm-transactions.gif b/versioned_docs/version-0.12 (stable)/miden-base/img/state/amm-transactions.gif new file mode 100644 index 0000000..c307b14 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-base/img/state/amm-transactions.gif differ diff --git a/versioned_docs/version-0.12 (stable)/miden-base/img/state/note-db.png b/versioned_docs/version-0.12 (stable)/miden-base/img/state/note-db.png new file mode 100644 index 0000000..6c1f640 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-base/img/state/note-db.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-base/img/state/nullifier-db.png b/versioned_docs/version-0.12 (stable)/miden-base/img/state/nullifier-db.png new file mode 100644 index 0000000..0a3ec0b Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-base/img/state/nullifier-db.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-base/img/state/public-shared-state.png b/versioned_docs/version-0.12 (stable)/miden-base/img/state/public-shared-state.png new file mode 100644 index 0000000..4a19a42 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-base/img/state/public-shared-state.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-base/img/state/state.png b/versioned_docs/version-0.12 (stable)/miden-base/img/state/state.png new file mode 100644 index 0000000..8405810 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-base/img/state/state.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-base/img/transaction/local-vs-network-transaction.png b/versioned_docs/version-0.12 (stable)/miden-base/img/transaction/local-vs-network-transaction.png new file mode 100644 index 0000000..8cce10e Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-base/img/transaction/local-vs-network-transaction.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-base/img/transaction/memory-layout-kernel.png b/versioned_docs/version-0.12 (stable)/miden-base/img/transaction/memory-layout-kernel.png new file mode 100644 index 0000000..3fc05fd Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-base/img/transaction/memory-layout-kernel.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-base/img/transaction/one-transfer-two-transactions.gif b/versioned_docs/version-0.12 (stable)/miden-base/img/transaction/one-transfer-two-transactions.gif new file mode 100644 index 0000000..e77517b Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-base/img/transaction/one-transfer-two-transactions.gif differ diff --git a/versioned_docs/version-0.12 (stable)/miden-base/img/transaction/transaction-contexts.png b/versioned_docs/version-0.12 (stable)/miden-base/img/transaction/transaction-contexts.png new file mode 100644 index 0000000..d8c6ea8 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-base/img/transaction/transaction-contexts.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-base/img/transaction/transaction-diagram.png b/versioned_docs/version-0.12 (stable)/miden-base/img/transaction/transaction-diagram.png new file mode 100644 index 0000000..53bf493 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-base/img/transaction/transaction-diagram.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-base/img/transaction/transaction-execution-process.png b/versioned_docs/version-0.12 (stable)/miden-base/img/transaction/transaction-execution-process.png new file mode 100644 index 0000000..faa2d10 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-base/img/transaction/transaction-execution-process.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-base/img/transaction/transaction-flow.png b/versioned_docs/version-0.12 (stable)/miden-base/img/transaction/transaction-flow.png new file mode 100644 index 0000000..168844e Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-base/img/transaction/transaction-flow.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-base/img/transaction/transaction-program.png b/versioned_docs/version-0.12 (stable)/miden-base/img/transaction/transaction-program.png new file mode 100644 index 0000000..cd867ce Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-base/img/transaction/transaction-program.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-base/img/transaction/tx-overview.png b/versioned_docs/version-0.12 (stable)/miden-base/img/transaction/tx-overview.png new file mode 100644 index 0000000..f2b0a59 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-base/img/transaction/tx-overview.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-base/index.md b/versioned_docs/version-0.12 (stable)/miden-base/index.md new file mode 100644 index 0000000..947d63c --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-base/index.md @@ -0,0 +1,70 @@ +--- +sidebar_position: 1 +title: Overview +--- + +# Miden Architecture Overview + +Miden’s architecture departs considerably from typical blockchain designs to support privacy and parallel transaction execution. + +In traditional blockchains, state and transactions must be transparent to be verifiable. This is necessary for block production and execution. + +However, user generated zero-knowledge proofs allow state transitions, e.g. transactions, to be verifiable without being transparent. + +## Miden design goals + +- High throughput: The ability to process a high number of transactions (state changes) over a given time interval. +- Privacy: The ability to keep data known to one’s self and anonymous while processing and/or storing it. +- Asset safety: Maintaining a low risk of mistakes or malicious behavior leading to asset loss. + +## Actor model + +The [actor model](https://en.wikipedia.org/wiki/Actor_model) inspires Miden’s execution model. This is a well-known computational design paradigm in concurrent systems. In the actor model, actors are state machines responsible for maintaining their own state. In the context of Miden, each account is an actor. Actors communicate with each other by exchanging messages asynchronously. One actor can send a message to another, but it is up to the recipient to apply the requested change to their state. + +Miden’s architecture takes the actor model further and combines it with zero-knowledge proofs. Now, actors not only maintain and update their own state, but they can also prove the validity of their own state transitions to the rest of the network. This ability to independently prove state transitions enables local smart contract execution, private smart contracts, and much more. And it is quite unique in the rollup space. Normally only centralized entities - sequencer or prover - create zero-knowledge proofs, not the users. + +## Core concepts + +Miden uses _accounts_ and _notes_, both of which hold assets. Accounts consume and produce notes during transactions. Transactions describe the account state changes of single accounts. + +### Accounts + +An [Account](account/index.md) can hold assets and define rules how assets can be transferred. Accounts can represent users or autonomous smart contracts. The [account chapter](account/index.md) describes the design of an account, its storage types, and creating an account. + +### Notes + +A [Note](note) is a message that accounts send to each other. A note stores assets and a script that defines how the note can be consumed. The [note chapter](note) describes the design, the storage types, and the creation of a note. + +### Assets + +An [Asset](asset) can be fungible and non-fungible. They are stored in the owner's account itself or in a note. The [asset chapter](asset) describes asset issuance, customization, and storage. + +### Transactions + +A [Transactions](transaction) describe the production and consumption of notes by a single account. + +Executing a transaction always results in a STARK proof. + +The [transaction chapter](transaction) describes the transaction design and implementation, including an in-depth discussion of how transaction execution happens in the transaction kernel program. + +#### Accounts produce and consume notes to communicate + +![Architecture core concepts](img/miden-architecture-core-concepts.gif) + +## State and execution + +The actor-based execution model requires a radically different approach to recording the system's state. Actors and the messages they exchange must be treated as first-class citizens. Miden addresses this by combining the state models of account-based systems like Ethereum and UTXO-based systems like Bitcoin and Zcash. + +Miden's state model captures the individual states of all accounts and notes, and the execution model describes state progress in a sequence of blocks. + +### State model + +[State](state) describes everything that is the case at a certain point in time. Individual states of accounts or notes can be stored on-chain and off-chain. This chapter describes the three different state databases in Miden. + +### Blockchain + +The [Blockchain](blockchain) defines how state progresses as aggregated-state-updates in batches, blocks, and epochs. The [blockchain chapter](blockchain) describes the execution model and how blocks are built. + +##### Operators capture and progress state + +![Architecture state process](img/miden-architecture-state-progress.gif) diff --git a/versioned_docs/version-0.12 (stable)/miden-base/note.md b/versioned_docs/version-0.12 (stable)/miden-base/note.md new file mode 100644 index 0000000..785133e --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-base/note.md @@ -0,0 +1,213 @@ +--- +sidebar_position: 3 +--- + +# Notes + +A `Note` is the medium through which [Accounts](account/index.md) communicate. A `Note` holds assets and defines how they can be consumed. + +## What is the purpose of a note? + +In Miden's hybrid UTXO and account-based model notes represent UTXO's which enable parallel transaction execution and privacy through asynchronous local `Note` production and consumption. + +## Note core components + +A `Note` is composed of several core components, illustrated below: + +

+ Note diagram +

+ +These components are: + +1. [Assets](#assets) +2. [Script](#script) +3. [Inputs](#inputs) +4. [Serial number](#serial-number) +5. [Metadata](#metadata) + +### Assets + +:::note +An [asset](asset) container for a `Note`. +::: + +A `Note` can contain from 0 up to 256 different assets. These assets represent fungible or non-fungible tokens, enabling flexible asset transfers. + +### Script + +:::note +The code executed when the `Note` is consumed. +::: + +Each `Note` has a script that defines the conditions under which it can be consumed. When accounts consume notes in transactions, `Note` scripts call the account’s interface functions. This enables all sorts of operations beyond simple asset transfers. The Miden VM’s Turing completeness allows for arbitrary logic, making `Note` scripts highly versatile. There is no limit to the amount of code a `Note` can hold. + +### Inputs + +:::note +Arguments passed to the `Note` script during execution. +::: + +A `Note` can have up to 128 input values, which adds up to a maximum of 1 KB of data. The `Note` script can access these inputs. They can convey arbitrary parameters for `Note` consumption. + +### Serial number + +:::note +A unique and immutable identifier for the `Note`. +::: + +The serial number has two main purposes. Firstly by adding some randomness to the `Note` it ensures it's uniqueness, secondly in private notes it helps prevent linkability between the note's hash and its nullifier. The serial number should be a random 32 bytes number chosen by the user. If leaked, the note’s nullifier can be easily computed, potentially compromising privacy. + +### Metadata + +:::note +Additional `Note` information. +::: + +Notes include metadata such as the sender’s account ID and a [tag](#note-discovery) that aids in discovery. Regardless of [storage mode](#note-storage-mode), these metadata fields remain public. + +## Note Lifecycle + +

+ Note lifecycle +

+ +The `Note` lifecycle proceeds through four primary phases: **creation**, **validation**, **discovery**, and **consumption**. Creation and consumption requires two separate transactions. Throughout this process, notes function as secure, privacy-preserving vehicles for asset transfers and logic execution. + +### Note creation + +Accounts can create notes in a transaction. The `Note` exists if it is included in the global notes DB. + +- **Users:** Executing local or network transactions. +- **Miden operators:** Facilitating on-chain actions, e.g. such as executing user notes against a DEX or other contracts. + +#### Note storage mode + +As with [accounts](account/index.md), notes can be stored either publicly or privately: + +- **Public mode:** The `Note` data is stored in the [note database](state#note-database), making it fully visible on-chain. +- **Private mode:** Only the `Note`’s hash is stored publicly. The `Note`’s actual data remains off-chain, enhancing privacy. + +### Note validation + +Once created, a `Note` must be validated by a Miden operator. Validation involves checking the transaction proof that produced the `Note` to ensure it meets all protocol requirements. + +After validation notes become "live" and eligible for consumption. If creation and consumption happens within the same block, there is no entry in the Notes DB. All other notes, are being added either as a commitment or fully public. + +### Note discovery + +Clients often need to find specific notes of interest. Miden allows clients to query the `Note` database using `Note` tags. These lightweight, 32-bit data fields serve as best-effort filters, enabling quick lookups for notes related to particular use cases, scripts, or account prefixes. + +Using `Note` tags strikes a balance between privacy and efficiency. Without tags, querying a specific `Note` ID reveals a user’s interest to the operator. Conversely, downloading and filtering all registered notes locally is highly inefficient. Tags allow users to adjust their level of privacy by choosing how broadly or narrowly they define their search criteria, letting them find the right balance between revealing too much information and incurring excessive computational overhead. + +### Note consumption + +To consume a `Note`, the consumer must know its data, including the inputs needed to compute the nullifier. Consumption occurs as part of a transaction. Upon successful consumption a nullifier is generated for the consumed notes. + +Upon successful verification of the transaction: + +1. The Miden operator records the `Note`’s nullifier as "consumed" in the nullifier database. +2. The `Note`’s one-time claim is thus extinguished, preventing reuse. + +#### Note recipient restricting consumption + +Consumption of a `Note` can be restricted to certain accounts or entities. For instance, the P2ID and P2IDE `Note` scripts target a specific account ID. Alternatively, Miden defines a RECIPIENT (represented as 32 bytes) computed as: + +```arduino +hash(hash(hash(serial_num, [0; 4]), script_root), input_commitment) +``` + +Only those who know the RECIPIENT’s pre-image can consume the `Note`. For private notes, this ensures an additional layer of control and privacy, as only parties with the correct data can claim the `Note`. + +The [transaction prologue](transaction) requires all necessary data to compute the `Note` hash. This setup allows scenario-specific restrictions on who may consume a `Note`. + +For a practical example, refer to the [SWAP note script](https://github.com/0xMiden/miden-base/blob/next/crates/miden-lib/asm/note_scripts/SWAP.masm), where the RECIPIENT ensures that only a defined target can consume the swapped asset. + +#### Note nullifier ensuring private consumption + +The `Note` nullifier, computed as: + +```arduino +hash(serial_num, script_root, input_commitment, vault_hash) +``` + +This achieves the following properties: + +- Every `Note` can be reduced to a single unique nullifier. +- One cannot derive a note's hash from its nullifier. +- To compute the nullifier, one must know all components of the `Note`: serial_num, script_root, input_commitment, and vault_hash. + +That means if a `Note` is private and the operator stores only the note's hash, only those with the `Note` details know if this `Note` has been consumed already. Zcash first [introduced](https://zcash.github.io/orchard/design/nullifiers.html#nullifiers) this approach. + +

+ Nullifier diagram +

+ +## Standard Note Types + +The miden-base repository provides several standard note scripts that implement common use cases for asset transfers and interactions. These pre-built note types offer secure, tested implementations for typical scenarios. + +### P2ID (Pay-to-ID) + +The P2ID note script implements a simple pay-to-account-ID pattern. It adds all assets from the note to a specific target account. + +**Key characteristics:** + +- **Purpose:** Direct asset transfer to a specific account ID +- **Inputs:** Requires exactly 2 note inputs containing the target account ID +- **Validation:** Ensures the consuming account's ID matches the target account ID specified in the note +- **Requirements:** Target account must expose the `miden::contracts::wallets::basic::receive_asset` procedure + +**Use case:** Simple, direct payments where you want to send assets to a known account ID. + +### P2IDE (Pay-to-ID Extended) + +The P2IDE note script extends P2ID with additional features including time-locking and reclaim functionality. + +**Key characteristics:** + +- **Purpose:** Advanced asset transfer with time-lock and reclaim capabilities +- **Inputs:** Requires exactly 4 note inputs: + - Target account ID + - Reclaim block height (when sender can reclaim) + - Time-lock block height (when target can consume) +- **Time-lock:** Note cannot be consumed until the specified block height is reached +- **Reclaim:** Original sender can reclaim the note after the reclaim block height if not consumed by target +- **Validation:** Complex logic to handle both target consumption and sender reclaim scenarios +- **Requirements:** Account must expose the `miden::contracts::wallets::basic::receive_asset` procedure + +**Use cases:** + +- Escrow-like payments with time constraints +- Conditional payments that can be reclaimed if not consumed +- Time-delayed transfers + +### SWAP + +The SWAP note script implements atomic asset swapping functionality. + +**Key characteristics:** + +- **Purpose:** Atomic asset exchange between two parties +- **Inputs:** Requires exactly 12 note inputs specifying: + - Requested asset details + - Payback note recipient information + - Note creation parameters (execution hint, type, aux data, tag) +- **Assets:** Must contain exactly 1 asset to be swapped +- **Mechanism:** + 1. Creates a payback note containing the requested asset for the original note issuer + 2. Adds the note's asset to the consuming account's vault +- **Requirements:** Account must expose both: + - `miden::contracts::wallets::basic::receive_asset` procedure + - `miden::contracts::wallets::basic::move_asset_to_note` procedure + +**Use case:** Decentralized asset trading where two parties want to exchange different assets atomically. + +### Choosing the Right Note Type + +- **Use P2ID** for simple, direct payments to known accounts +- **Use P2IDE** when you need time-locks, escrow functionality, or reclaim capabilities +- **Use SWAP** for atomic asset exchanges between parties +- **Create custom scripts** for specialized use cases not covered by standard types + +These standard note types provide a foundation for common operations while maintaining the flexibility to create custom note scripts for specialized requirements. diff --git a/versioned_docs/version-0.12 (stable)/miden-base/protocol_library.md b/versioned_docs/version-0.12 (stable)/miden-base/protocol_library.md new file mode 100644 index 0000000..aa49c87 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-base/protocol_library.md @@ -0,0 +1,164 @@ +--- +sidebar_position: 8 +--- + +# Miden Protocol Library + +The Miden protocol library provides a set of procedures that wrap transaction kernel procedures to provide a more convenient interface for common operations. These can be invoked by account code, note scripts, and transaction scripts, though some have restriction from where they can be called. The procedures are organized into modules corresponding to different functional areas. + +## Contexts + +Here and in other places we use the notion of _active account_: it is the account which is currently being accessed. + +The Miden VM contexts from which procedures can be called are: + +- **Account**: Can only be called from native or foreign accounts. + - **Native**: Can only be called when the active account is the native account. + - **Auth**: Can only be called from the authentication procedure. Since it is called on the native account, it implies **Native** and **Account**. + - **Faucet**: Can only be called when the active account is a faucet. +- **Note**: Can only be called from a note script. +- **Any**: Can be called from any context. + +If a procedure has multiple context requirements they are combined using `&`. For instance, "Native & Account" means the procedure can only be called when the active account is the native one _and_ only from the account context. + +## Implementation + +Most procedures in the Miden protocol library are implemented as wrappers around underlying kernel procedures. They handle the necessary stack padding and cleanup operations required by the kernel interface, providing a more convenient API for developers. + +The procedures maintain the same security and context restrictions as the underlying kernel procedures. When invoking these procedures, ensure that the calling context matches the requirements. + +## Active account Procedures (`miden::active_account`) + +Active account procedures can be used to read from storage, fetch or compute commitments or obtain other internal data of the active account. + +| Procedure | Description | Context | +| -------------------------------- | ----------------------------- | ----------------------------- | +| `get_id` | Returns the ID of the active account.

**Inputs:** `[]`
**Outputs:** `[account_id_prefix, account_id_suffix]` | Any | +| `get_nonce` | Returns the nonce of the active account. Always returns the initial nonce as it can only be incremented in auth procedures.

**Inputs:** `[]`
**Outputs:** `[nonce]` | Any | +| `get_initial_commitment` | Returns the active account commitment at the beginning of the transaction.

**Inputs:** `[]`
**Outputs:** `[INIT_COMMITMENT]` | Any | +| `compute_commitment` | Computes and returns the account commitment from account data stored in memory.

**Inputs:** `[]`
**Outputs:** `[ACCOUNT_COMMITMENT]` | Any | +| `get_code_commitment` | Gets the account code commitment of the active account.

**Inputs:** `[]`
**Outputs:** `[CODE_COMMITMENT]` | Account | +| `get_initial_storage_commitment` | Returns the storage commitment of the active account at the beginning of the transaction.

**Inputs:** `[]`
**Outputs:** `[INIT_STORAGE_COMMITMENT]` | Any | +| `compute_storage_commitment` | Computes the latest account storage commitment of the active account.

**Inputs:** `[]`
**Outputs:** `[STORAGE_COMMITMENT]` | Account | +| `get_item` | Gets an item from the account storage.

**Inputs:** `[index]`
**Outputs:** `[VALUE]` | Account | +| `get_initial_item` | Gets the initial item from the account storage slot as it was at the beginning of the transaction.

**Inputs:** `[index]`
**Outputs:** `[VALUE]` | Account | +| `get_map_item` | Returns the VALUE located under the specified KEY within the map contained in the given account storage slot.

**Inputs:** `[index, KEY]`
**Outputs:** `[VALUE]` | Account | +| `get_initial_map_item` | Gets the initial VALUE from the account storage map as it was at the beginning of the transaction.

**Inputs:** `[index, KEY]`
**Outputs:** `[VALUE]` | Account | +| `get_balance` | Returns the balance of the fungible asset associated with the provided faucet_id in the active account's vault.

**Inputs:** `[faucet_id_prefix, faucet_id_suffix]`
**Outputs:** `[balance]` | Any | +| `get_initial_balance` | Returns the balance of the fungible asset associated with the provided faucet_id in the active account's vault at the beginning of the transaction.

**Inputs:** `[faucet_id_prefix, faucet_id_suffix]`
**Outputs:** `[init_balance]` | Any | +| `has_non_fungible_asset` | Returns a boolean indicating whether the non-fungible asset is present in the active account's vault.

**Inputs:** `[ASSET]`
**Outputs:** `[has_asset]` | Any | +| `get_initial_vault_root` | Returns the vault root of the active account at the beginning of the transaction.

**Inputs:** `[]`
**Outputs:** `[INIT_VAULT_ROOT]` | Any | +| `get_vault_root` | Returns the vault root of the active account.

**Inputs:** `[]`
**Outputs:** `[VAULT_ROOT]` | Any | +| `get_num_procedures` | Returns the number of procedures in the active account.

**Inputs:** `[]`
**Outputs:** `[num_procedures]` | Any | +| `get_procedure_root` | Returns the procedure root for the procedure at the specified index.

**Inputs:** `[index]`
**Outputs:** `[PROC_ROOT]` | Any | +| `has_procedure` | Returns the binary flag indicating whether the procedure with the provided root is available on the active account.

**Inputs:** `[PROC_ROOT]`
**Outputs:** `[is_procedure_available]` | Any | + +## Native account Procedures (`miden::native_account`) + +Native account procedures can be used to write to storage, add or remove assets from the vault and compute delta commitment of the native account. + +| Procedure | Description | Context | +| ------------------------------ | ------------------------------ | ------------------------------ | +| `get_id` | Returns the ID of the native account of the transaction.

**Inputs:** `[]`
**Outputs:** `[account_id_prefix, account_id_suffix]` | Any | +| `incr_nonce` | Increments the nonce of the native account by one and returns the new nonce. Can only be called from auth procedures.

**Inputs:** `[]`
**Outputs:** `[final_nonce]` | Auth | +| `compute_delta_commitment` | Computes the commitment to the native account's delta. Can only be called from auth procedures.

**Inputs:** `[]`
**Outputs:** `[DELTA_COMMITMENT]` | Auth | +| `set_item` | Sets an item in the native account storage.

**Inputs:** `[index, VALUE]`
**Outputs:** `[OLD_VALUE]` | Native & Account | +| `set_map_item` | Sets VALUE under the specified KEY within the map contained in the given native account storage slot.

**Inputs:** `[index, KEY, VALUE]`
**Outputs:** `[OLD_MAP_ROOT, OLD_MAP_VALUE]` | Native & Account | +| `add_asset` | Adds the specified asset to the vault. For fungible assets, returns the total after addition.

**Inputs:** `[ASSET]`
**Outputs:** `[ASSET']` | Native & Account | +| `remove_asset` | Removes the specified asset from the vault.

**Inputs:** `[ASSET]`
**Outputs:** `[ASSET]` | Native & Account | +| `was_procedure_called` | Returns 1 if a native account procedure was called during transaction execution, and 0 otherwise.

**Inputs:** `[PROC_ROOT]`
**Outputs:** `[was_called]` | Any | + +## Active Note Procedures (`miden::active_note`) + +Active note procedures can be used to fetch data from the note that is currently being processed by the transaction kernel. + +| Procedure | Description | Context | +| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | +| `get_assets` | Writes the [assets](note.md#assets) of the active note into memory starting at the specified address.

**Inputs:** `[dest_ptr]`
**Outputs:** `[num_assets, dest_ptr]` | Note | +| `get_recipient` | Returns the [recipient](note.md#note-recipient-restricting-consumption) of the active note.

**Inputs:** `[]`
**Outputs:** `[RECIPIENT]` | Note | +| `get_inputs` | Writes the note's [inputs](note.md#inputs) to the specified memory address.

**Inputs:** `[dest_ptr]`
**Outputs:** `[num_inputs, dest_ptr]` | Note | +| `get_metadata` | Returns the [metadata](note.md#metadata) of the active note.

**Inputs:** `[]`
**Outputs:** `[METADATA]` | Note | +| `get_sender` | Returns the sender of the active note.

**Inputs:** `[]`
**Outputs:** `[sender_id_prefix, sender_id_suffix]` | Note | +| `get_serial_number` | Returns the [serial number](note.md#serial-number) of the active note.

**Inputs:** `[]`
**Outputs:** `[SERIAL_NUMBER]` | Note | +| `get_script_root` | Returns the [script root](note.md#script) of the active note.

**Inputs:** `[]`
**Outputs:** `[SCRIPT_ROOT]` | Note | +| `add_assets_to_account` | Adds all assets from the active note to the account vault.

**Inputs:** `[]`
**Outputs:** `[]` | Note | + +## Input Note Procedures (`miden::input_note`) + +Input note procedures can be used to fetch data on input notes consumed by the transaction. + +| Procedure | Description | Context | +| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------- | +| `get_assets_info` | Returns the information about [assets](note.md#assets) in the input note with the specified index.

**Inputs:** `[note_index]`
**Outputs:** `[ASSETS_COMMITMENT, num_assets]` | Any | +| `get_assets` | Writes the [assets](note.md#assets) of the input note with the specified index into memory starting at the specified address.

**Inputs:** `[dest_ptr, note_index]`
**Outputs:** `[num_assets, dest_ptr, note_index]` | Any | +| `get_recipient` | Returns the [recipient](note.md#note-recipient-restricting-consumption) of the input note with the specified index.

**Inputs:** `[note_index]`
**Outputs:** `[RECIPIENT]` | Any | +| `get_metadata` | Returns the [metadata](note.md#metadata) of the input note with the specified index.

**Inputs:** `[note_index]`
**Outputs:** `[METADATA]` | Any | +| `get_sender` | Returns the sender of the input note with the specified index.

**Inputs:** `[note_index]`
**Outputs:** `[sender_id_prefix, sender_id_suffix]` | Any | +| `get_inputs_info` | Returns the [inputs](note.md#inputs) commitment and length of the input note with the specified index.

**Inputs:** `[note_index]`
**Outputs:** `[NOTE_INPUTS_COMMITMENT, num_inputs]` | Any | +| `get_script_root` | Returns the [script root](note.md#script) of the input note with the specified index.

**Inputs:** `[note_index]`
**Outputs:** `[SCRIPT_ROOT]` | Any | +| `get_serial_number` | Returns the [serial number](note.md#serial-number) of the input note with the specified index.

**Inputs:** `[note_index]`
**Outputs:** `[SERIAL_NUMBER]` | Any | + +## Output Note Procedures (`miden::output_note`) + +Output note procedures can be used to fetch data on output notes created by the transaction. + +| Procedure | Description | Context | +| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | +| `create` | Creates a new output note and returns its index.

**Inputs:** `[tag, aux, note_type, execution_hint, RECIPIENT]`
**Outputs:** `[note_idx]` | Native & Account | +| `get_assets_info` | Returns the information about assets in the output note with the specified index.

**Inputs:** `[note_index]`
**Outputs:** `[ASSETS_COMMITMENT, num_assets]` | Any | +| `get_assets` | Writes the assets of the output note with the specified index into memory starting at the specified address.

**Inputs:** `[dest_ptr, note_index]`
**Outputs:** `[num_assets, dest_ptr, note_index]` | Any | +| `add_asset` | Adds the `ASSET` to the output note specified by the index.

**Inputs:** `[ASSET, note_idx]`
**Outputs:** `[]` | Native | +| `get_recipient` | Returns the [recipient](note#note-recipient-restricting-consumption) of the output note with the specified index.

**Inputs:** `[note_index]`
**Outputs:** `[RECIPIENT]` | Any | +| `get_metadata` | Returns the [metadata](note#metadata) of the output note with the specified index.

**Inputs:** `[note_index]`
**Outputs:** `[METADATA]` | Any | + +## Note Utility Procedures (`miden::note`) + +Note utility procedures can be used to compute the required utility data or write note data to memory. + +| Procedure | Description | Context | +| --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | +| `compute_inputs_commitment` | Computes the commitment to the output note inputs starting at the specified memory address.

**Inputs:** `[inputs_ptr, num_inputs]`
**Outputs:** `[INPUTS_COMMITMENT]` | Any | +| `get_max_inputs_per_note` | Returns the max allowed number of input values per note.

**Inputs:** `[]`
**Outputs:** `[max_inputs_per_note]` | Any | +| `write_assets_to_memory` | Writes the assets data stored in the advice map to the memory specified by the provided destination pointer.

**Inputs:** `[ASSETS_COMMITMENT, num_assets, dest_ptr]`
**Outputs:** `[num_assets, dest_ptr]` | Any | +| `build_recipient_hash` | Returns the `RECIPIENT` for a specified `SERIAL_NUM`, `SCRIPT_ROOT`, and inputs commitment.

**Inputs:** `[SERIAL_NUM, SCRIPT_ROOT, INPUT_COMMITMENT]`
**Outputs:** `[RECIPIENT]` | Any | +| `build_recipient` | Builds the recipient hash from note inputs, script root, and serial number.

**Inputs:** `[inputs_ptr, num_inputs, SERIAL_NUM, SCRIPT_ROOT]`
**Outputs:** `[RECIPIENT]` | Any | +| `extract_sender_from_metadata` | Extracts the sender ID from the provided metadata word.

**Inputs:** `[METADATA]`
**Outputs:** `[sender_id_prefix, sender_id_suffix]` | Any | + +## Transaction Procedures (`miden::tx`) + +Transaction procedures manage transaction-level operations including note creation, context switching, and reading transaction metadata. + +| Procedure | Description | Context | +| ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | +| `get_block_number` | Returns the block number of the transaction reference block.

**Inputs:** `[]`
**Outputs:** `[num]` | Any | +| `get_block_commitment` | Returns the block commitment of the reference block.

**Inputs:** `[]`
**Outputs:** `[BLOCK_COMMITMENT]` | Any | +| `get_block_timestamp` | Returns the timestamp of the reference block for this transaction.

**Inputs:** `[]`
**Outputs:** `[timestamp]` | Any | +| `get_input_notes_commitment` | Returns the input notes commitment hash.

**Inputs:** `[]`
**Outputs:** `[INPUT_NOTES_COMMITMENT]` | Any | +| `get_output_notes_commitment` | Returns the output notes commitment hash.

**Inputs:** `[]`
**Outputs:** `[OUTPUT_NOTES_COMMITMENT]` | Any | +| `get_num_input_notes` | Returns the total number of input notes consumed by this transaction.

**Inputs:** `[]`
**Outputs:** `[num_input_notes]` | Any | +| `get_num_output_notes` | Returns the current number of output notes created in this transaction.

**Inputs:** `[]`
**Outputs:** `[num_output_notes]` | Any | +| `execute_foreign_procedure` | Executes the provided procedure against the foreign account.

**Inputs:** `[foreign_account_id_prefix, foreign_account_id_suffix, FOREIGN_PROC_ROOT, , pad(n)]`
**Outputs:** `[]` | Any | +| `get_expiration_block_delta` | Returns the transaction expiration delta, or 0 if not set.

**Inputs:** `[]`
**Outputs:** `[block_height_delta]` | Any | +| `update_expiration_block_delta` | Updates the transaction expiration delta.

**Inputs:** `[block_height_delta]`
**Outputs:** `[]` | Any | + +## Faucet Procedures (`miden::faucet`) + +Faucet procedures allow reading and writing to faucet accounts to mint and burn assets. + +| Procedure | Description | Context | +| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------- | +| `create_fungible_asset` | Creates a fungible asset for the faucet the transaction is being executed against.

**Inputs:** `[amount]`
**Outputs:** `[ASSET]` | Faucet | +| `create_non_fungible_asset` | Creates a non-fungible asset for the faucet the transaction is being executed against.

**Inputs:** `[DATA_HASH]`
**Outputs:** `[ASSET]` | Faucet | +| `mint` | Mint an asset from the faucet the transaction is being executed against.

**Inputs:** `[ASSET]`
**Outputs:** `[ASSET]` | Native & Account & Faucet | +| `burn` | Burn an asset from the faucet the transaction is being executed against.

**Inputs:** `[ASSET]`
**Outputs:** `[ASSET]` | Native & Account & Faucet | +| `get_total_issuance` | Returns the total issuance of the fungible faucet the transaction is being executed against.

**Inputs:** `[]`
**Outputs:** `[total_issuance]` | Faucet | +| `is_non_fungible_asset_issued` | Returns a boolean indicating whether the provided non-fungible asset has been already issued by this faucet.

**Inputs:** `[ASSET]`
**Outputs:** `[is_issued]` | Faucet | + +## Asset Procedures (`miden::asset`) + +Asset procedures provide utilities for creating fungible and non-fungible assets. + +| Procedure | Description | Context | +| -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | +| `build_fungible_asset` | Builds a fungible asset for the specified fungible faucet and amount.

**Inputs:** `[faucet_id_prefix, faucet_id_suffix, amount]`
**Outputs:** `[ASSET]` | Any | +| `build_non_fungible_asset` | Builds a non-fungible asset for the specified non-fungible faucet and data hash.

**Inputs:** `[faucet_id_prefix, DATA_HASH]`
**Outputs:** `[ASSET]` | Any | diff --git a/versioned_docs/version-0.12 (stable)/miden-base/state.md b/versioned_docs/version-0.12 (stable)/miden-base/state.md new file mode 100644 index 0000000..6257959 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-base/state.md @@ -0,0 +1,131 @@ +--- +sidebar_position: 6 +--- + +# State + +The `State` describes the current condition of all accounts, notes, nullifiers and their statuses. Reflecting the "current reality" of the protocol at any given time. + +## What is the purpose of the Miden state model? + +By employing a concurrent `State` model with local execution and proving, Miden achieves three primary properties: preserving privacy, supporting parallel transactions, and reducing state-bloat by minimizing on-chain data storage. + +Miden’s `State` model focuses on: + +- **Concurrency:** + Multiple transactions can be processed concurrently by distinct actors using local transaction execution which improves throughput and efficiency. + +- **Flexible data storage:** + Users can store data privately on their own devices or within the network. This approach reduces reliance on the network for data availability, helps maintain user sovereignty, and minimizes unnecessary on-chain storage. + +- **Privacy:** + By using notes and nullifiers, Miden ensures that value transfers remain confidential. Zero-knowledge proofs allow users to prove correctness without revealing sensitive information. + +## State model components + +The Miden node maintains three databases to describe `State`: + +1. Accounts +2. Notes +3. Nullifiers + +

+ State +

+ +### Account database + +The accounts database has two main purposes: + +1. Track state commitments of all accounts +2. Store account data for public accounts + +This is done using an authenticated data structure, a sparse Merkle tree. + +

+ Account DB +

+ +As described in the [account ID section](account/id#account-storage-mode), accounts can have different storage modes: + +- **Public & Network accounts:** where all account data is stored on-chain. +- **Private accounts:** where only the commitments to the account is stored on-chain. + +Private accounts significantly reduce storage overhead. A private account contributes only 40 bytes to the global `State` (15 bytes for the account ID + 32 bytes for the account commitment + 4 bytes for the block number). For example, 1 billion private accounts take up only 47.47 GB of `State`. + +The storage contribution of a public account depends on the amount of data it stores. + +:::warning +In Miden, when the user is the custodian of their account `State` (in the case of a private account), losing this `State` amounts to losing their funds, similar to losing a private key. +::: + +### Note database + +As described in the [notes section](note), there are two types of notes: + +- **Public notes:** where the entire note content is stored on-chain. +- **Private notes:** where only the note’s commitment is stored on-chain. + +Private notes greatly reduce storage requirements and thus result in lower fees. At high throughput (e.g., 1K TPS), the note database could grow by about 1TB/year. However, only unconsumed public notes and enough information to construct membership proofs must be stored explicitly. Private notes, as well as consumed public notes, can be discarded. This solves the issue of infinitely growing note databases. + +Notes are recorded in an append-only accumulator, a [Merkle Mountain Range](https://github.com/opentimestamps/opentimestamps-server/blob/master/doc/merkle-mountain-range.md). + +Using a Merkle Mountain Range (append-only accumulator) is important for two reasons: + +1. Membership witnesses (that a note exists in the database) against such an accumulator needs to be updated very infrequently. +2. Old membership witnesses can be extended to a new accumulator value, but this extension does not need to be done by the original witness holder. + +Both of these properties are needed for supporting local transactions using client-side proofs and privacy. In an append-only data structure, witness data does not become stale when the data structure is updated. That means users can generate valid proofs even if they don’t have the latest `State` of this database; so there is no need to query the operator on a constantly changing `State`. + +

+ Note DB +

+ +### Nullifier database + +Each [note](note) has an associated nullifier which enables the tracking of whether its associated note has been consumed or not, preventing double-spending. + +To prove that a note has not been consumed, the operator must provide a Merkle path to the corresponding node and show that the node’s value is 0. Since nullifiers are 32 bytes each, the sparse Merkle tree height must be sufficient to represent all possible nullifiers. Operators must maintain the entire nullifier set to compute the new tree root after inserting new nullifiers. For each nullifier we also record the block in which it was created. This way "unconsumed" nullifiers have block 0, but all consumed nullifiers have a non-zero block. + +:::note +Nullifiers in Miden break linkability between privately stored notes and their consumption details. To know the [note's nullifier](note#note-nullifier-ensuring-private-consumption), one must know the note's data. +::: + +

+ Nullifier DB +

+ +## Additional information + +### Public shared state + +In most blockchains, most smart contracts and decentralized applications (e.g., AAVE, Uniswap) need public shared `State`. Public shared `State` is also available on Miden and can be represented as in the following example: + +

+ Example: AMM transactions +

+ +In this diagram, multiple participants interact with a common, publicly accessible `State` (the AMM in the center). The figure illustrates how notes are created and consumed: + +1. **Independent Transactions Creating Notes (tx1 & tx2):** + Two separate users (Acc1 and Acc2) execute transactions independently: + - **tx1** creates **note1** + - **tx2** creates **note2** + + These transactions occur in parallel and do not rely on each other, allowing concurrent processing without contention. + +2. **Sequencing and Consuming Notes (tx3):** + The Miden node executes tx3 against the shared account, consuming **note1 & note2** and creating **note3 & note4**. tx3 is a network transaction executed by the Miden operator. It merges independent contributions into a unified `State` update. + +3. **Further Independent Transactions (tx4 & tx5):** + After the shared `State` is updated: + - **tx4** consumes **note4** + - **tx5** consumes **note3** + + Both users can now interact independently and in parallel with notes generated by the public account, continuing the cycle of `State` evolution. + +### State bloat minimization + +Miden nodes do not need to know the entire `State` to verify or produce new blocks. Rather than storing the full `State` data with the nodes, users keep their data locally, and the rollup stores only commitments to that data. While some contracts must remain publicly visible, this approach minimizes `State` bloat. Furthermore the Miden rollup can discard non-required data after certain conditions have been met. + +This ensures that the account and note databases remain manageable, even under sustained high usage. diff --git a/versioned_docs/version-0.12 (stable)/miden-base/theme/Admonition/Icon/Danger.tsx b/versioned_docs/version-0.12 (stable)/miden-base/theme/Admonition/Icon/Danger.tsx new file mode 100644 index 0000000..ab14d7d --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-base/theme/Admonition/Icon/Danger.tsx @@ -0,0 +1,19 @@ +import React, { type ReactNode } from "react"; +import type { Props } from "@theme/Admonition/Icon/Danger"; + +export default function AdmonitionIconDanger(props: Props): ReactNode { + return ( + + + + ); +} diff --git a/versioned_docs/version-0.12 (stable)/miden-base/theme/Admonition/Icon/Info.tsx b/versioned_docs/version-0.12 (stable)/miden-base/theme/Admonition/Icon/Info.tsx new file mode 100644 index 0000000..59e48a5 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-base/theme/Admonition/Icon/Info.tsx @@ -0,0 +1,20 @@ +import React, { type ReactNode } from "react"; +import type { Props } from "@theme/Admonition/Icon/Info"; + +export default function AdmonitionIconInfo(props: Props): ReactNode { + return ( + + + + ); +} diff --git a/versioned_docs/version-0.12 (stable)/miden-base/theme/Admonition/Icon/Note.tsx b/versioned_docs/version-0.12 (stable)/miden-base/theme/Admonition/Icon/Note.tsx new file mode 100644 index 0000000..d7c524b --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-base/theme/Admonition/Icon/Note.tsx @@ -0,0 +1,20 @@ +import React, { type ReactNode } from "react"; +import type { Props } from "@theme/Admonition/Icon/Note"; + +export default function AdmonitionIconNote(props: Props): ReactNode { + return ( + + + + ); +} diff --git a/versioned_docs/version-0.12 (stable)/miden-base/theme/Admonition/Icon/Tip.tsx b/versioned_docs/version-0.12 (stable)/miden-base/theme/Admonition/Icon/Tip.tsx new file mode 100644 index 0000000..219bb8d --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-base/theme/Admonition/Icon/Tip.tsx @@ -0,0 +1,20 @@ +import React, { type ReactNode } from "react"; +import type { Props } from "@theme/Admonition/Icon/Tip"; + +export default function AdmonitionIconTip(props: Props): ReactNode { + return ( + + + + ); +} diff --git a/versioned_docs/version-0.12 (stable)/miden-base/theme/Admonition/Icon/Warning.tsx b/versioned_docs/version-0.12 (stable)/miden-base/theme/Admonition/Icon/Warning.tsx new file mode 100644 index 0000000..f96398d --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-base/theme/Admonition/Icon/Warning.tsx @@ -0,0 +1,20 @@ +import React, { type ReactNode } from "react"; +import type { Props } from "@theme/Admonition/Icon/Warning"; + +export default function AdmonitionIconCaution(props: Props): ReactNode { + return ( + + + + ); +} diff --git a/versioned_docs/version-0.12 (stable)/miden-base/theme/Admonition/Layout/index.tsx b/versioned_docs/version-0.12 (stable)/miden-base/theme/Admonition/Layout/index.tsx new file mode 100644 index 0000000..7b2c170 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-base/theme/Admonition/Layout/index.tsx @@ -0,0 +1,51 @@ +import React, { type ReactNode } from "react"; +import clsx from "clsx"; +import { ThemeClassNames } from "@docusaurus/theme-common"; + +import type { Props } from "@theme/Admonition/Layout"; + +import styles from "./styles.module.css"; + +function AdmonitionContainer({ + type, + className, + children, +}: Pick & { children: ReactNode }) { + return ( +
+ {children} +
+ ); +} + +function AdmonitionHeading({ icon, title }: Pick) { + return ( +
+ {icon} + {/* {title} */} +
+ ); +} + +function AdmonitionContent({ children }: Pick) { + return children ? ( +
{children}
+ ) : null; +} + +export default function AdmonitionLayout(props: Props): ReactNode { + const { type, icon, title, children, className } = props; + return ( + + {title || icon ? : null} + {children} + + ); +} diff --git a/versioned_docs/version-0.12 (stable)/miden-base/theme/Admonition/Layout/styles.module.css b/versioned_docs/version-0.12 (stable)/miden-base/theme/Admonition/Layout/styles.module.css new file mode 100644 index 0000000..88df7e6 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-base/theme/Admonition/Layout/styles.module.css @@ -0,0 +1,35 @@ +.admonition { + margin-bottom: 1em; +} + +.admonitionHeading { + font: var(--ifm-heading-font-weight) var(--ifm-h5-font-size) / + var(--ifm-heading-line-height) var(--ifm-heading-font-family); + text-transform: uppercase; +} + +/* Heading alone without content (does not handle fragment content) */ +.admonitionHeading:not(:last-child) { + margin-bottom: 0.3rem; +} + +.admonitionHeading code { + text-transform: none; +} + +.admonitionIcon { + display: inline-block; + vertical-align: middle; + margin-right: 0.4em; +} + +.admonitionIcon svg { + display: inline-block; + height: 1.6em; + width: 1.6em; + fill: var(--ifm-alert-foreground-color); +} + +.admonitionContent > :last-child { + margin-bottom: 0; +} diff --git a/versioned_docs/version-0.12 (stable)/miden-base/theme/Admonition/Type/Caution.tsx b/versioned_docs/version-0.12 (stable)/miden-base/theme/Admonition/Type/Caution.tsx new file mode 100644 index 0000000..b570a37 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-base/theme/Admonition/Type/Caution.tsx @@ -0,0 +1,32 @@ +import React, {type ReactNode} from 'react'; +import clsx from 'clsx'; +import Translate from '@docusaurus/Translate'; +import type {Props} from '@theme/Admonition/Type/Caution'; +import AdmonitionLayout from '@theme/Admonition/Layout'; +import IconWarning from '@theme/Admonition/Icon/Warning'; + +const infimaClassName = 'alert alert--warning'; + +const defaultProps = { + icon: , + title: ( + + caution + + ), +}; + +// TODO remove before v4: Caution replaced by Warning +// see https://github.com/facebook/docusaurus/issues/7558 +export default function AdmonitionTypeCaution(props: Props): ReactNode { + return ( + + {props.children} + + ); +} diff --git a/versioned_docs/version-0.12 (stable)/miden-base/theme/Admonition/Type/Danger.tsx b/versioned_docs/version-0.12 (stable)/miden-base/theme/Admonition/Type/Danger.tsx new file mode 100644 index 0000000..49901fa --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-base/theme/Admonition/Type/Danger.tsx @@ -0,0 +1,30 @@ +import React, {type ReactNode} from 'react'; +import clsx from 'clsx'; +import Translate from '@docusaurus/Translate'; +import type {Props} from '@theme/Admonition/Type/Danger'; +import AdmonitionLayout from '@theme/Admonition/Layout'; +import IconDanger from '@theme/Admonition/Icon/Danger'; + +const infimaClassName = 'alert alert--danger'; + +const defaultProps = { + icon: , + title: ( + + danger + + ), +}; + +export default function AdmonitionTypeDanger(props: Props): ReactNode { + return ( + + {props.children} + + ); +} diff --git a/versioned_docs/version-0.12 (stable)/miden-base/theme/Admonition/Type/Info.tsx b/versioned_docs/version-0.12 (stable)/miden-base/theme/Admonition/Type/Info.tsx new file mode 100644 index 0000000..018e0a1 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-base/theme/Admonition/Type/Info.tsx @@ -0,0 +1,30 @@ +import React, {type ReactNode} from 'react'; +import clsx from 'clsx'; +import Translate from '@docusaurus/Translate'; +import type {Props} from '@theme/Admonition/Type/Info'; +import AdmonitionLayout from '@theme/Admonition/Layout'; +import IconInfo from '@theme/Admonition/Icon/Info'; + +const infimaClassName = 'alert alert--info'; + +const defaultProps = { + icon: , + title: ( + + info + + ), +}; + +export default function AdmonitionTypeInfo(props: Props): ReactNode { + return ( + + {props.children} + + ); +} diff --git a/versioned_docs/version-0.12 (stable)/miden-base/theme/Admonition/Type/Note.tsx b/versioned_docs/version-0.12 (stable)/miden-base/theme/Admonition/Type/Note.tsx new file mode 100644 index 0000000..c99e038 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-base/theme/Admonition/Type/Note.tsx @@ -0,0 +1,30 @@ +import React, {type ReactNode} from 'react'; +import clsx from 'clsx'; +import Translate from '@docusaurus/Translate'; +import type {Props} from '@theme/Admonition/Type/Note'; +import AdmonitionLayout from '@theme/Admonition/Layout'; +import IconNote from '@theme/Admonition/Icon/Note'; + +const infimaClassName = 'alert alert--secondary'; + +const defaultProps = { + icon: , + title: ( + + note + + ), +}; + +export default function AdmonitionTypeNote(props: Props): ReactNode { + return ( + + {props.children} + + ); +} diff --git a/versioned_docs/version-0.12 (stable)/miden-base/theme/Admonition/Type/Tip.tsx b/versioned_docs/version-0.12 (stable)/miden-base/theme/Admonition/Type/Tip.tsx new file mode 100644 index 0000000..18604a5 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-base/theme/Admonition/Type/Tip.tsx @@ -0,0 +1,30 @@ +import React, {type ReactNode} from 'react'; +import clsx from 'clsx'; +import Translate from '@docusaurus/Translate'; +import type {Props} from '@theme/Admonition/Type/Tip'; +import AdmonitionLayout from '@theme/Admonition/Layout'; +import IconTip from '@theme/Admonition/Icon/Tip'; + +const infimaClassName = 'alert alert--success'; + +const defaultProps = { + icon: , + title: ( + + tip + + ), +}; + +export default function AdmonitionTypeTip(props: Props): ReactNode { + return ( + + {props.children} + + ); +} diff --git a/versioned_docs/version-0.12 (stable)/miden-base/theme/Admonition/Type/Warning.tsx b/versioned_docs/version-0.12 (stable)/miden-base/theme/Admonition/Type/Warning.tsx new file mode 100644 index 0000000..61d9597 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-base/theme/Admonition/Type/Warning.tsx @@ -0,0 +1,30 @@ +import React, {type ReactNode} from 'react'; +import clsx from 'clsx'; +import Translate from '@docusaurus/Translate'; +import type {Props} from '@theme/Admonition/Type/Warning'; +import AdmonitionLayout from '@theme/Admonition/Layout'; +import IconWarning from '@theme/Admonition/Icon/Warning'; + +const infimaClassName = 'alert alert--warning'; + +const defaultProps = { + icon: , + title: ( + + warning + + ), +}; + +export default function AdmonitionTypeWarning(props: Props): ReactNode { + return ( + + {props.children} + + ); +} diff --git a/versioned_docs/version-0.12 (stable)/miden-base/theme/Admonition/Types.tsx b/versioned_docs/version-0.12 (stable)/miden-base/theme/Admonition/Types.tsx new file mode 100644 index 0000000..2a10019 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-base/theme/Admonition/Types.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import AdmonitionTypeNote from '@theme/Admonition/Type/Note'; +import AdmonitionTypeTip from '@theme/Admonition/Type/Tip'; +import AdmonitionTypeInfo from '@theme/Admonition/Type/Info'; +import AdmonitionTypeWarning from '@theme/Admonition/Type/Warning'; +import AdmonitionTypeDanger from '@theme/Admonition/Type/Danger'; +import AdmonitionTypeCaution from '@theme/Admonition/Type/Caution'; +import type AdmonitionTypes from '@theme/Admonition/Types'; + +const admonitionTypes: typeof AdmonitionTypes = { + note: AdmonitionTypeNote, + tip: AdmonitionTypeTip, + info: AdmonitionTypeInfo, + warning: AdmonitionTypeWarning, + danger: AdmonitionTypeDanger, +}; + +// Undocumented legacy admonition type aliases +// Provide hardcoded/untranslated retrocompatible label +// See also https://github.com/facebook/docusaurus/issues/7767 +const admonitionAliases: typeof AdmonitionTypes = { + secondary: (props) => , + important: (props) => , + success: (props) => , + caution: AdmonitionTypeCaution, +}; + +export default { + ...admonitionTypes, + ...admonitionAliases, +}; diff --git a/versioned_docs/version-0.12 (stable)/miden-base/theme/Admonition/index.tsx b/versioned_docs/version-0.12 (stable)/miden-base/theme/Admonition/index.tsx new file mode 100644 index 0000000..8f4225d --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-base/theme/Admonition/index.tsx @@ -0,0 +1,21 @@ +import React, {type ComponentType, type ReactNode} from 'react'; +import {processAdmonitionProps} from '@docusaurus/theme-common'; +import type {Props} from '@theme/Admonition'; +import AdmonitionTypes from '@theme/Admonition/Types'; + +function getAdmonitionTypeComponent(type: string): ComponentType { + const component = AdmonitionTypes[type]; + if (component) { + return component; + } + console.warn( + `No admonition component found for admonition type "${type}". Using Info as fallback.`, + ); + return AdmonitionTypes.info!; +} + +export default function Admonition(unprocessedProps: Props): ReactNode { + const props = processAdmonitionProps(unprocessedProps); + const AdmonitionTypeComponent = getAdmonitionTypeComponent(props.type); + return ; +} diff --git a/versioned_docs/version-0.12 (stable)/miden-base/transaction.md b/versioned_docs/version-0.12 (stable)/miden-base/transaction.md new file mode 100644 index 0000000..72fe743 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-base/transaction.md @@ -0,0 +1,142 @@ +--- +sidebar_position: 5 +--- + +# Transactions + +A `Transaction` in Miden is the state transition of a single account. A `Transaction` takes as input a single [account](./account) and zero or more [notes](note), and outputs the same account with an updated state, together with zero or more notes. Transactions in Miden are Miden VM programs, their execution resulting in the generation of a zero-knowledge proof. + +Miden's `Transaction` model aims for the following: + +- **Parallel transaction execution**: Accounts can update their state independently from each other and in parallel. +- **Private transaction execution**: Client-side `Transaction` proving allows the network to verify transactions validity with zero-knowledge. + +

+ Transaction diagram +

+ +Compared to most blockchains, where a `Transaction` typically involves more than one account (e.g., sender and receiver), a `Transaction` in Miden involves a single account. To illustrate, Alice sends 5 ETH to Bob. In Miden, sending 5 ETH from Alice to Bob takes two transactions, one in which Alice creates a note containing 5 ETH and one in which Bob consumes that note and receives the 5 ETH. This model removes the need for a global lock on the blockchain's state, enabling Miden to process transactions in parallel. + +Currently the protocol limits the number of notes that can be consumed and produced in a transaction to 1000 each, which means that in a single `Transaction` an application could serve up to 2000 different user requests like deposits or withdrawals into/from a pool. + +A simple transaction currently takes about 1-2 seconds on a MacBook Pro. It takes around `90K` cycles to create the proof, as of now the signature verification step is the dominant cost. + +## Transaction lifecycle + +Every `Transaction` describes the process of an account changing its state. This process is described as a Miden VM program, resulting in the generation of a zero-knowledge proof. Transactions are being executed in a specified sequence, in which several notes and a transaction script can interact with an account. + +

+ Transaction program +

+ +### Inputs + +A `Transaction` requires several inputs: + +- **Account**: A `Transaction` is always executed against a single account. The executor must have complete knowledge of the account's state. +- **Notes**: A `Transaction` can consume and output up to `1024` notes. The executor must have complete knowledge of the note data, including note inputs, before consumption. For private notes, the data cannot be fetched from the blockchain and must be received through an off-chain channel. +- **Blockchain state**: The current reference block and information about the notes database used to authenticate notes to be consumed must be retrieved from the Miden operator before execution. Usually, notes to be consumed in a `Transaction` must have been created before the reference block. +- **Transaction script (optional)**: The `Transaction` script is code defined by the executor. And like note scripts, they can invoke account methods, e.g., sign a transaction. There is no limit to the amount of code a `Transaction` script can hold. +- **Transaction arguments (optional)**: For every note, the executor can inject transaction arguments that are present at runtime. If the note script — and therefore the note creator — allows, the note script can read those arguments to allow dynamic execution. See below for an example. +- **Foreign account data (optional)**: Any foreign account data accessed during a `Transaction`, whether private or public, must be available beforehand. There is no need to know the full account storage, but the data necessary for the `Transaction`, e.g., the key/value pair that is read and the corresponding storage root. + +### Flow + +1. **Prologue** + Executes at the beginning of a transaction. It validates on-chain commitments against the provided data. This is to ensure that the transaction executes against a valid on-chain recorded state of the account and to be consumed notes. Notes to be consumed must be registered on-chain — except for [erasable notes](note) which can be consumed without block inclusion. +2. **Note processing** + Notes are executed sequentially against the account, following a sequence defined by the executor. To execute a note means processing the note script that calls methods exposed on the account interface. Notes must be consumed fully, which means that all assets must be transferred into the account or into other created notes. [Note scripts](note#script) can invoke the account interface during execution. They can push assets into the account's vault, create new notes, set a transaction expiration, and read from or write to the account's storage. Any method they call must be explicitly exposed by the account interface. Note scripts can also invoke methods of foreign accounts to read their state. +3. **Transaction script processing** + `Transaction` scripts are an optional piece of code defined by the executor which interacts with account methods after all notes have been executed. For example, `Transaction` scripts can be used to sign the `Transaction` (e.g., sign the transaction by incrementing the nonce of the account, without which, the transaction would fail), to mint tokens from a faucet, create notes, or modify account storage. `Transaction` scripts can also invoke methods of foreign accounts to read their state. +4. **Epilogue** + Completes the execution, resulting in an updated account state and a generated zero-knowledge proof. The validity of the resulting transaction is ensured by a combination of user-defined and protocol-defined checks: + - The account's [authentication procedure](account/code#authentication) is called to authorize the transaction. + - The transaction fee is computed and removed from the account's vault in the chain's native asset. See [Fees](fees). + - The account's state must have changed, or at least one input note must have been consumed to make the transaction non-empty. + - If the account's state has changed, the `nonce` must have been incremented to prevent replay attacks. + - Additionally, the sum of all input assets must be equal to the sum of all output assets (if the account is not a faucet). + +The proof together with the corresponding data needed for verification and updates of the global state can then be submitted and processed by the network. + +## Examples + +To illustrate the `Transaction` protocol, we provide two examples for a basic `Transaction`. We will use references to the existing Miden `Transaction` kernel — the reference implementation of the protocol — and to the methods in Miden Assembly. + +### Creating a P2ID note + +Let's assume account A wants to create a P2ID note. P2ID notes are pay-to-ID notes that can only be consumed by a specified target account ID. Note creators can provide the target account ID using the [note inputs](note#inputs). + +In this example, account A uses the basic wallet and the authentication component provided by `miden-lib`. The basic wallet component defines the methods `wallets::basic::create_note` and `wallets::basic::move_asset_to_note` to create notes with assets, and `wallets::basic::receive_asset` to receive assets. The authentication component exposes `auth::basic::auth_tx_rpo_falcon512` which allows for signing a transaction. Some account methods like `active_account::get_id` are always exposed. + +The executor inputs to the Miden VM a `Transaction` script in which he places on the stack the data (tag, aux, note_type, execution_hint, RECIPIENT) of the note(s) that he wants to create using `wallets::basic::create_note` during the said `Transaction`. The [`NoteRecipient`](https://github.com/0xMiden/miden-base/blob/main/crates/miden-objects/src/note/recipient.rs) is a value that describes under which condition a note can be consumed and is built using a `serial_number`, the `note_script` (in this case P2ID script) and the `note_inputs`. The Miden VM will execute the `Transaction` script and create the note(s). After having been created, the executor can use `wallets::basic::move_asset_to_note` to move assets from the account's vault to the notes vault. + +After finalizing the `Transaction` the updated state and created note(s) can now be submitted to the Miden operator to be recorded on-chain. + +### Consuming a P2ID note + +Let's now assume that account A wants to consume a P2ID note to receive the assets contained in that note. + +To start the transaction process, the executor fetches and prepares all the input data to the `Transaction`. First, it retrieves blockchain data, like global inputs and block data of the most recent block. This information is needed to authenticate the native account's state and that the P2ID note exists on-chain. Then it loads the full account and note data, to start the `Transaction` execution. + +In the transaction's prologue the data is being authenticated by re-hashing the provided values and comparing them to the blockchain's data (this is how private data can be used and verified during the execution of transaction without actually revealing it to the network). + +Then the P2ID note script is being executed. The script starts by reading the note inputs `active_note::get_inputs` — in our case the account ID of the intended target account. It checks if the provided target account ID equals the account ID of the executing account. This is the first time the note invokes a method exposed by the `Transaction` kernel, `active_account::get_id`. + +If the check passes, the note script pushes the assets it holds into the account's vault. For every asset the note contains, the script calls the `wallets::basic::receive_asset` method exposed by the account's wallet component. The `wallets::basic::receive_asset` procedure calls `native_account::add_asset`, which cannot be called from the note itself. This allows accounts to control what functionality to expose, e.g. whether the account supports receiving assets or not, and the note cannot bypass that. + +After the assets are stored in the account's vault, the transaction script is being executed. The script calls `auth::basic::auth_tx_rpo_falcon512` which is explicitly exposed in the account interface. The method is used to verify a provided signature against a public key stored in the account's storage and a commitment to this specific transaction. If the signature can be verified, the method increments the nonce. + +The Epilogue finalizes the transaction by computing the final account hash, asserting the nonce increment and checking that no assets were created or destroyed in the transaction — that means the net sum of all assets must stay the same. + +## Transaction types + +There are two types of transactions in Miden: **local transactions** and **network transactions** [not yet implemented]. + +### Local transaction + +Users transition their account's state locally using the Miden VM and generate a `Transaction` proof that can be verified by the network, which we call **client-side proving**. The network then only has to verify the proof and to change the global parts of the state to apply the state transition. + +They are useful, because: + +1. They enable privacy as neither the account state nor account code are needed to verify the zero-knowledge proof. Public inputs are only commitments and block information that are stored on-chain. +2. They are cheaper (i.e., lower in fees) as the execution of the state transition and the generation of the zero-knowledge proof are already made by the users. Hence **privacy is the cheaper option on Miden**. +3. They allow arbitrarily complex computation to be done. The proof size doesn't grow linearly with the complexity of the computation. Hence there is no gas limit for client-side proving. + +Client-side proving or local transactions on low-power devices can be slow, but Miden offers a pragmatic alternative: **delegated proving**. Instead of waiting for complex computations to finish on your device, you can hand off proof generation to a service, ensuring a consistent 1-2 second proving time, even on mobile. + +### Network transaction + +The Miden operator executes the `Transaction` and generates the proof. Miden uses network transactions for smart contracts with public shared state. This type of `Transaction` is quite similar to the ones in traditional blockchains (e.g., Ethereum). + +They are useful, because: + +1. For public shared state of smart contracts. Network transactions allow orchestrated state changes of public smart contracts without race conditions. +2. Smart contracts should be able to be executed autonomously, ensuring liveness. Local transactions require a user to execute and prove, but in some cases a smart contract should be able to execute when certain conditions are met. +3. Clients may not have sufficient resources to generate zero-knowledge proofs. + +The ability to facilitate both, local and network transactions, **is one of the differentiating factors of Miden** compared to other blockchains. Local `Transaction` execution and proving can happen in parallel as for most transactions there is no need for public state changes. This increases the network's throughput tremendously and provides privacy. Network transactions on the other hand enable autonomous smart contracts and public shared state. + +--- + +:::tip + +- Usually, notes that are consumed in a `Transaction` must be recorded on-chain in order for the `Transaction` to succeed. However, Miden supports **erasable notes** which are notes that can be consumed in a `Transaction` before being registered on-chain. For example, one can build a sub-second order book by allowing its traders to build faster transactions that depend on each other and are being validated or erased in batches. + +- There is no nullifier check during a `Transaction`. Nullifiers are checked by the Miden operator during `Transaction` verification. So at the local level, there is "double spending." If a note was already spent, i.e. there exists a nullifier for that note, the block producer would never include the `Transaction` as it would make the block invalid. + +- One of the main reasons for separating execution and proving steps is to allow _stateless provers_; i.e., the executed `Transaction` has all the data it needs to re-execute and prove a `Transaction` without database access. This supports easier proof-generation distribution. + +- Not all transactions require notes. For example, the owner of a faucet can mint new tokens using only a `Transaction` script, without interacting with external notes. + +- In Miden executors can choose arbitrary reference blocks to execute against their state. Hence it is possible to set `Transaction` expiration heights and in doing so, to define a block height until a `Transaction` should be included into a block. If the `Transaction` is expired, the resulting account state change is not valid and the `Transaction` cannot be verified anymore. + +- Note and `Transaction` scripts can read the state of foreign accounts during execution. This is called foreign procedure invocation. For example, the price of an asset for the **Swap** script might depend on a certain value stored in the oracle account. + +- An example of the right usage of `Transaction` arguments is the consumption of a **Swap** note. Those notes allow asset exchange based on predefined conditions. Example: + - The note's consumption condition is defined as "anyone can consume this note to take `X` units of asset A if they simultaneously create a note sending Y units of asset B back to the creator." If an executor wants to buy only a fraction `(X-m)` of asset A, they provide this amount via transaction arguments. The executor would provide the value `m`. The note script then enforces the correct transfer: + - A new note is created returning `Y-((m*Y)/X)` of asset B to the sender. + - A second note is created, holding the remaining `(X-m)` of asset A for future consumption. + +- When executing a `Transaction` the max number of VM cycles is **$2^{30}$**. + +::: diff --git a/versioned_docs/version-0.12 (stable)/miden-node/_category_.yml b/versioned_docs/version-0.12 (stable)/miden-node/_category_.yml new file mode 100644 index 0000000..5bd2d64 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-node/_category_.yml @@ -0,0 +1,4 @@ +label: "Node" +# Determines where this documentation section appears relative to other sections on the main documentation page (which is the parent of this folder in the miden-docs repository) +position: 6 +collapsed: true diff --git a/versioned_docs/version-0.12 (stable)/miden-node/img/node_architecture.svg b/versioned_docs/version-0.12 (stable)/miden-node/img/node_architecture.svg new file mode 100644 index 0000000..ba697ed --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-node/img/node_architecture.svg @@ -0,0 +1,4 @@ + + +eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nO19WXfiyJb1+/1cdTAwMTVe1Y9foY556Dc8z/P8VS8vwFx1MDAxOGMzmcHG7nX/e58jXHUwMDBmXGIpJCRQkmQvXFy36mZcdTAwMWFcdTAwMTBcdTAwMTFS7HP2mf/nXysrf/XfO9W//mvlr+qwUmrU77ult7/+xt+/Vru9ertcdTAwMDUvMf/vvfagW/Hf+djvd3r/9Z//Wep0vNGnvEq7+fnJaqParLb6PXjv/4e/r6z8j/9feKV+j5/fIOv7tWHlcv9071rrouaN0+O6/1H/Td9cdTAwMGLqViv9UqvWqI5eXHUwMDFhwu8ptcojllx1MDAxM6aY1YLzn1ff4VUpqPC0ZZpcdTAwMTmrLGH259W3+n3/XHUwMDExP6+ZJy3X8ueVx2q99tiHl6z1tIBcdTAwMGZxXHUwMDAxL1x1MDAxM0JGl/5cXMd/rZCf3/T63fZzda3daHdxsf9Bq/jPaKnlUuW51m1cdTAwMGZa9z/v6XdLrV6n1IV7M3rfQ73ROOu/+1eHe1xm9/Kv0HdcXH0vPPT7uE/Bl9ZcdTAwMWVb1V5v7DPtTqlS7+MtomS0XHUwMDBiXFxhZ+fef1T/PVpTt9Ss7uCzalxyXHUwMDFhjZ9f11v3VXxcdTAwMDJ/lcXB2Ne17r++buztvWpcdTAwMTUvQVxyXHUwMDEzVnK4rz+vjI5cdTAwMTY8TFx1MDAxM/71YbvlnzNcdTAwMDNPiWrJ2GhcdTAwMDG9dThbff+yXHUwMDBmpUavOrrbuIiNwLlcdTAwMWJtZtC5L31+XHUwMDA0L8ZcdTAwMTWnTFx1MDAxODl6+o166zm89Ea78jz6XHUwMDE2/7f//tt1lE376GC3d7Y7eLo1xb3zi/c9ubqX/ihr7jFtpbBcdTAwMWHuXHUwMDBmh+M8fpa1klx1MDAxZTH4XHUwMDAyp8QwXHUwMDE1OctGWVx1MDAwZs669lx1MDAwZmz0PHNDPFx1MDAxYvjR8z7PlW671ys8lvqVxz/gVJtt96n+fuajXHUwMDA3yr9+8+/IYVdGwYGnMnKq4UVBZNxZZ9pwIanI+ahzq6Tg+Vx1MDAxY/X1gjxcdTAwMTPi7uqj3W1cdTAwMWPQ2+5up2BthqNOPUlcdTAwMDSVllx1MDAxMus46iCuPa6kskpYw11H3dDoXHUwMDAxXHUwMDE3jHtcdTAwMWGkvVVcdTAwMDZcdTAwMDQ3/IjlXHRPPOG7s59wRqRcdTAwMTJcdTAwMDLOueOEc6ZjTzilijEltcn5jEuhVFx1MDAwMDgznfGBum2R182zrdftTqV0/PJsb2t3qc94XHUwMDAxZLlcdTAwMDesQUlcdTAwMDBcdTAwMWUhSpNcdTAwMTA3oUJ4Qlx1MDAxM2qMMtIlz5nRcIHRj+PIXHUwMDAzRDyqlYZcdTAwMWJJmVxccpT4s34481mHO8xcZlx1MDAwN+7iOOpSxlxuc9DVVILO1lMxl5/ljVx1MDAxNlx1MDAxYTh6/epw9GBcdTAwMDInl9dcdTAwMGXLjV6ldNlsXHUwMDE3Lq/ahf7HqX7+6+d9//7bfdnPXHUwMDBmV9hdbevoqdFcdTAwMTfnW6u73cfuXHUwMDAzK6yOf8v395e63fZb4Lpff4rFp1x1MDAwMUmvXGbNSVx1MDAwN7l3XHUwMDE5wefYTfqEJlXC41x1MDAwNiRcdTAwMTe1VFxuYFZhZNpkZErtacWt1kRcdTAwMTJcdTAwMWZ5XHUwMDBltiWXUIyB4lFcdTAwMTZzwVrJZJBcdTAwMDJcdTAwMDRAR2ks6JRCaahcdTAwMDJPNlx1MDAwZv0yxflcdTAwMWRcdTAwMWRHPIaw/9PjtcBcdTAwMDNst/pn9Vx1MDAwZl8lmrHfbpaa9YZ/XHUwMDFhx65QbNRrLVx1MDAxZqWw2Gr3r+BccujXwfr+eUO/3Vx1MDAxOb1agSuW6q1qdyeNZmt367V6q9Q4dy25NOi3T6u9z0X3u4Nq8GZUt7/PP/WYTMBuspBcdFx0mHHwXG6tXHUwMDEy9aqiXGbVplCMMu5cIo9wnjyGMlnB85Q6wM5/0Es8XHUwMDAzp4sqMLU4MfDoRydtieZxNFx1MDAxZqdVrCyWRFx1MDAwMmlcdTAwMTNoiDtBLmJBLlxymMOKk1xc7SSpLaWaaFx1MDAxYTzWaTHeadfDenv0p5XRkfH/8vPn//7b+e7YY4o/hehcdFx1MDAxZF0vooZcdTAwMWKlXn+t3WzW+7DRY1xcZETe9kvd/io81nqrXHUwMDE2fq3aulx1MDAxZr1cdTAwMTJ4rl+evU+RcvXQ2nqrV2/uqjeMdl/3zyv7RydcdTAwMDHWXHUwMDAwQq0ywFtcdTAwMDMrJ1x1MDAwMF240WBcdTAwMDRcdTAwMTB47pRpXHUwMDExeF+t1IF3Wdg6XHUwMDExRlxiRlx1MDAwNFx1MDAwMzUtoodcdTAwMDdXXFxE+fBYLUVcdTAwMGUorDn4XHUwMDFhXHUwMDAwrlx1MDAxZeLo1Ua5/ZaKZ9TPzl5qW1fdXHUwMDE3XbiSz2+Vl6tXvZ2KZ1xiIcHOjVx1MDAxN1XSXHUwMDE4eD1BVFHuIP1sXHUwMDA02qUwXHUwMDFhXHUwMDE3RidcdTAwMTmoXHUwMDA1UVZKbqMuR19FRFj+t9RcdTAwMTEodpRcIvkyXHUwMDBisIZcdTAwMDHNbCZm0Vx1MDAxYpRcdTAwMDHhK51u+7XaWulcdTAwMGadNIOqsd/+SpoxtvYwp4hfbD5cdTAwMDSjt/5eulx1MDAxMN33gXku1fZ443BrvZrFeGfUU9pcblx1MDAwZarJhVxcZpinwHCINVx1MDAxMahcdTAwMWE5P36QS+koXHUwMDAysYTuOHRP00PXXHUwMDAw+lxic2BcdTAwMTTtLsvCv/1xOoHeIUDg2VROp1x1MDAxZlxylc1cdTAwMTLnj6u2VnzrvOyWrpvDQ3N4/vKQ1lx1MDAxMj842NrfPTvrbeuLer32PJD86kDkZIlzn1fk5Clz7zKNhlx1MDAwNFx1MDAwM8+zQnNt4MGAJpQhnHHGPJqIM8I9yUGRXHUwMDFhpalxxT3EUl3GYe4sPeZcdTAwMThRXHUwMDAyfbZO0Fx0XHUwMDEyb4ljuEpcdTAwMWJcdTAwMWF4XG65WOLZz29EX5ZLvXplpVt9XHUwMDE5VHv9f1qw8PpcdTAwMDPotj7uaVx1MDAxZaqzWb+/XHUwMDBmKp6QkT5Bg4VcdTAwMTXq2G5W3JvJR7UmU/3JqpVcdTAwMDJitTbA/52qVVxu41FhjVZ0qVpzgfl5XHUwMDE2h1x1MDAxYkVggUXi0q0qPmRJMVx1MDAwM4OxqWzx6VTrdUOdic29w4+W7OqD5tXjaaN8O1x1MDAwNyd34nVf+Eapd25qW++6M3xb2zh7fumc5KKyv1x1MDAxY1x1MDAxM8pcdTAwMDYxO73Kdt+9dCrbYJqBgbVQXHUwMDAxf6EshF+rPZWIX1xub8D4rLJcdTAwMDJEOFx1MDAwYiB3aeNORvNFXHUwMDA2NHNhXGI8XHUwMDA36sq24SY22Vx1MDAwNlx1MDAxZVx1MDAxYyOcSp6rkfvtWiNcdTAwMTlOcERp+4rtXHUwMDFkXGZGNFx1MDAxZNtcdTAwMGaLoKgn6MOwoo7dQT7auf5Rurvr7WxVTlx1MDAwN7W702L/YLXddXBxt2fdeFx1MDAxY4lcdTAwMWXjnCpcdTAwMWHIn/jk4pp4VjOGOXHMXHUwMDEwM7qdP8i2wjOGK1xuXHUwMDA0XHLlgFx1MDAxOF1hpKg9XHUwMDA1XFzfgCFmkLBrtlxmlMUh/XJ21zrwdcpcdTAwMDHOzqA1jzWVlVx1MDAwNKhcbv0rnFxcnLDguf4trvX4c4o/hehcdTAwMTFcdTAwMWRdMKKlM/vW4zzoonV43uze7N/r3tCuX7/0d9bNR4xcdTAwMDddoXyWVEhcclx1MDAxYqCCXHUwMDA13vXpP8ctcFx1MDAwMoJfXHUwMDFhIVBcdTAwMTObyFx1MDAxMVx1MDAxOXPrR1x1MDAxNzxcdTAwMTfXunq+XFynR7esUlx1MDAxYlxcXHUwMDEy9fq0unN9V0znWvdcdTAwMWRcdTAwMDNcdTAwMWNzfi3nJpRAxoX2RKKosnCH8EfAXHUwMDE5kHArRyd9yUEmS6ar9Fx1MDAxY8RcdTAwMWFcdTAwMGJcdTAwMDREOyP4YMbHUlx1MDAxMK2U5EBgck74zcHPXHUwMDBlXHUwMDE2dvd9XHUwMDA1QNKvzoV+TOtid64zXHUwMDFmktF6a2xcdTAwMWW/1596u6/2tLt2fX/aOjqM4lx1MDAxNi9cdTAwMTnJijOJ0XskXHUwMDE5idF7qTwmXHUwMDE0cefsj1x1MDAwZctCI/a+jcpivpC9nplMSCWkYNqZXHUwMDAwx3S8212j61ZO51x1MDAxYUhwXHUwMDAwckKkncqWyJVLOM4j/rDR52anXGbx4fjp9fbYa512473mP8hJXHUwMDFhe1dcdTAwMWTcq4ve5VGRyf3nl4Pi4ep5K4r8uMRvZTysb2BcdTAwMWM0gogmflNhQPCAaKAmpsbBSE9IRaT4SvKOyoBAQHehhcBvUNs3MyfBgvkuiKFOc0LFxsxcdTAwMTlcdTAwMTVKwdOmOebAzlx1MDAxY1wi+zvpuvL65fytf77fOdxcdTAwMWFcdTAwMGVcbsMtfVYukVx1MDAxY67Lnnduj99cdTAwMWUvmlx1MDAxZNXZ5Y3VrUvNXHUwMDFm0l03XCJGot5cdTAwMTVGXHLLYl0l4Hxt7eisNdx+6eyc6sfO5eZd4eRcIn38nDGEKVx1MDAwMJlY46pl4tp6XHUwMDE2Y35aXHUwMDBiZ4FcdTAwMDfGXHUwMDA1YzJr0X1cdTAwMThMi59/0vtcdTAwMWZW5lHK4DCkzEpBpXG5/42JredQhFqpxTzd/82WXHUwMDFlKFNsXZ2b7YP76+vtfbW+k92dbvKBi3s1XHUwMDExuERcclmmMJecKcYtKEUlQz43oelcdTAwMDSgXHUwMDEw6dGlITstNspZsIHpQNKdMObwsP2kqTLGjJKL50qvt1x1MDAxZVx1MDAxYXhC5mjKlsHw+SpcZvffXHUwMDEw8qRPUDphXHUwMDAzN25cdTAwMDP52LjJ1Fwi2ZFux5PHQvUlXFxcdTAwMTJPcUPQxKVERJ1TXHUwMDE4XCJcdTAwMGaWdVx1MDAwNVTcSFxySu5cdFxm1FrB4Ep/XG7r/Vx1MDAxZKZvJVx1MDAwM8ilJlxmZKlwKkBcdTAwMTVxYv0wXFyrXHUwMDE0k4xMleUynVx1MDAwMmRbr6dk/fH1eGv3vXk5rNrti+bg1/LFgL2YpzeegVxu5GD0mXCnhVx1MDAxYz5eiOBk0vXiwTfpepE7nltsIDlcdTAwMTdoZVx1MDAxNFx1MDAxYiCeYCBUODVEYXxgVFwitfJcdTAwMWRcdTAwMWHgwY/Vh6OFjd1UjzBrXHUwMDA1XHUwMDAxpadcdTAwMTVcdTAwMTHajj1cdTAwMTJPasrQoDNcbsu3mY3cg5gwQ9z+kr1cbmP7I4RcdTAwMTE0QeHrleSEXHUwMDA3XHUwMDFmz9dcdTAwMGVlmlx1MDAxZGJcdTAwMWRcdTAwMDJcdTAwMTYvK4yQ+50ghFx1MDAxY9+ksMC7rLTUcKXBklFcdLvML2Qypq/81Z9Va5Ns4S9Zx8bEhL+m0K4nwSWItzGEfO5cIvPVXHUwMDEy4DK6jS5h5S9+p3fWqVbqpUZEXHUwMDAwwVpcIi8m6HK3rExBz1x1MDAwYkLBaZdcdTAwMTTsWFx0XHUwMDFh28ApXHUwMDE41+WUKo9cdTAwMWFBgZ+rMZY3lqBq4Fx1MDAxNkmj4bTBvVhcdTAwMDSC/oeZsPfpNbjCRjFRRY2Cz4Z/+Vx1MDAxM2zCXHUwMDEwqcBypJxJOrGa6ZmCTZ1ue1iv3q/4wZxFyHaZQIvDXHUwMDFjPWb9+VD0pjk+vtg5Llx1MDAxZD1V7jf1y0dnIMub6Z1UIJo0XHUwMDA1ocQ1hpEjTipBmKcl6Fx1MDAxOVx1MDAxYdOFhHLlMVRcdTAwMWTM7amSy1x1MDAwNiTJsK66YZ2lXHUwMDAxXHRBmiac+S1KxkeXjYGHLuV0gJ+Or7+V1MFT326/7D/3P7b3RXmjrlx1MDAwYnPIK0287l3fljW/+nj62Hx87N2+2KLV5Vx1MDAxY677x/i53U8lXHUwMDA1M2BaeNxqgsRcdTAwMTBsP1x1MDAxNnbcUZssPDBcdTAwMDVcdFx1MDAwM1hGaFx1MDAwMVx1MDAxYlrWeWZcdTAwMTFcdTAwMWNcdTAwMGbp+Vx1MDAwMHZcdTAwMWFcdTAwMTLAv9xcdTAwMTJcItZrRy1cdTAwMDPLhoJ1kDMlyOx1jlCCr1x1MDAwNNL5ee0mMIJcdFo4Jv/1l/jsksVkss+OjlelhHg+QNlDOCumseekI6OMWeIxg8lm9tNcdTAwMTRyeOLh2p6likgmli0lk1x1MDAwMF7LXHUwMDAwcLib8HhcdTAwMTRzueyA18dcIlx1MDAxYzszXHUwMDExrqcrXHUwMDA3naSeplwi/fm61YT0qFx1MDAxMET4XHLxZGa3WuLHXHUwMDBi4ZM86XLx6JhwuVxiXHUwMDFkyM2plqllhYDTXCI1oVx1MDAxYZtvSlx1MDAxNmjG8Z1xXHUwMDFi/OCUbjWMvjLBpMKYXHUwMDExMTO71ZIl80rQraZcdTAwMDFcZtYwoTnaM46OXHUwMDFjwlx1MDAwM3PHKKGsIdhMUqTZL945XHUwMDBl2+WYsSNcdTAwMTjVmLUzvme4KFx1MDAxN1x1MDAxNlBcdTAwMDP/4M5cdTAwMTP2PCcnW/QsXHUwMDA1XU3h71xmv5agnbrPrfvas3yUR1x1MDAxN53D62OlOut7XHUwMDBlL1RsToVAlyScXHUwMDFmd39YuIlcdTAwMWXTmGlcdTAwMTdcdTAwMTcqXHUwMDA2XGLC4Vx1MDAxZPnqXHUwMDFjXG7KXHUwMDFhz1x1MDAxMKVcdTAwMDRRvut7mVsxQU89ZdBT2mpquCQuPUVJNKdq5J2iWCvH+Fx1MDAxYztcYlx1MDAwZfY/9tlcdTAwMDHdqDZ69avNYnW/vX5K0lx1MDAxYX9qfWetsS8363q7evSmjlYrhYPi+LcsRlx1MDAxMaR7l2mMP1BXoFx1MDAxY6lSlFx1MDAxOatlXHUwMDE4i5IrjFx1MDAwMIO2IMqZx0glXHUwMDAwXHLzNqRcdTAwMTJWgPZbpm1kgd1zXHUwMDE22GklLXNXNCN3jIVcdTAwMWRBfYRcXP9cdTAwMTdYgDllbviPtFTBKv/eXCKYglx1MDAxMzRcXGxcdTAwMDKHe1x1MDAxZvnYhLXbx5vtrep+dWPI1i7oS/O2XFxbz6R1XHUwMDE1XGJtaoQzk1FaQLo0XHUwMDAwZFx1MDAxNq91gS9yLKhlYqzv+lx1MDAwZtKN8L5f/fxZat1E+DczwF+BeCaGOJtcdTAwMGJSXHUwMDFhkLGRXHUwMDE2osKgbJ5nTqOs3fItWl1cdTAwMTdXu4+XXHUwMDFiV5dXldfzrbRa97DxsVs/erzevGj3d043mD5ZPXtKp3WTtXnR2lVxev642322XHUwMDA3O6JHO7t5pEJfPVx1MDAxZu2X1m9cdTAwMWaGXHUwMDFkUahcdTAwMTT35MExpyldz/NlXHTup5KGJYCS15xJXG7iXHUwMDFlLDtcdTAwMTJoXHUwMDEy/dmplFx1MDAxOWBcdFx1MDAwMqtcdTAwMTRjZFx1MDAwN2NcdTAwMWXni1x1MDAxNjv+Y1hCK4OYkFozIOjOUVxylIpYco5WK1CM6SpcdTAwMWLmQ1x1MDAxMsoo1qtcdTAwMGLBXHUwMDBmJujiWH5cdTAwMTDZQj7UYOPu7GHjo655QVx1MDAxZL5LVryi+qCcmlx1MDAxYWDzfY1TheBcYrDo6CHBPWxRxaWMMVx1MDAwMZajh2aHeDs9xFx1MDAxOVx1MDAwMzHrMLP9lcR3XHUwMDE5plx1MDAwNFx1MDAwYshcdTAwMTmnc+xcdTAwMWFYXHUwMDE2J6+NPblz0bw11ePBUat5uMbmYH3/XHUwMDE2frG5vbX3tLM/OD9fXHUwMDFkXlx1MDAxY91cdTAwMWM/XHUwMDE1XG67ezlcXPfooPZ8ctjudDbX7rffTyq11+t+PVx1MDAxN37BXHUwMDE1s1xcWZZcdTAwMTO/cD/tXHUwMDE0/EJcdTAwMTHpXHUwMDE5XHUwMDBl+lx1MDAwN+SPRVM1JIFcZveMYFqgZlx1MDAwYlx1MDAwZTlcdTAwMTilpmGSozRWcoPVO8tGTJlkTydcdTAwMDO9MCDMQZDEOCFsbKKKP9Mu6G3PqVx1MDAwYoKkUsyWmOar5JXyoN64XHUwMDBmMoLfxy0mKPNov0Tn+vMhXHUwMDE2yTI4KVx1MDAwZS2o9Fx1MDAxNFHKXHUwMDFhd4W05Fg7XCK4wVxu+GBcdTAwMTbMyLPIsPdLXGaz4Mqzflo7U1SZQMrz0tPgxPhL2lxmtfhcdTAwMTZMVGmmlHampJr4IXCKYT3flN7H6WhH7UVV2PXg/aW/9rRZWj9cdTAwMWVcdTAwMGXfVzNWlCzG6Fx1MDAwNFx1MDAxN1x1MDAwNvCnXHUwMDEwOf6jK0U2l1ucOdkpujJW3GAt2JqSXHUwMDEyqlx1MDAxNbGUXHUwMDA1guTfYebIOUtcdTAwMTVcdE5cdTAwMTaOY2vgXHUwMDA0TVx1MDAxNlxuVJlgYMJEQ8HUk5FVzK9nlPuUpqBLQlx1MDAxOTCrXGb69IxcdTAwMTCWh7wxwjKct6GJUERcdTAwMDBcdTAwMWaKSFahPOHPYYOjbrSxrpjN/JtN/2FcdTAwMDK1m540aVx1MDAwMmY1iEBX80pcdTAwMTYrN6mxyp9cdTAwMWTyK1x1MDAxMvdm88j0YFx1MDAxNZX+P63y+MP6fZRpXHUwMDAyTYnMbHCvP1x1MDAxZsqUbF4mUSaplMdGflx1MDAxOFx1MDAxNjKDpMBcYlxyt/BcbsGkzihjgjfo7zR+d09cdTAwMTlFPWpcdNFcdTAwMWH+XHUwMDAzxEzQZWZ/Msx7s/MmkMVcbrOtXFy0ScdcdTAwMTbiXHUwMDAyWFxy09O2rp2ON1x1MDAxZDSKx7Xb3fPC7eb1+8nTQVx1MDAwMbRt/0/kTYVYKOBPXHUwMDE0XHUwMDA082BPqZlLgXhUXHUwMDFiI5VcdTAwMDFcdTAwMWJcdOs6XHUwMDAzLcd+qFx1MDAwYndQl1RcdTAwMDQq2XM9RqAox+JYXHUwMDA1VrvEXHUwMDAxV9yVSydcYv+MXGL/TiblPrepmJT2XGYjmJjE4G6rXHUwMDEwk5JSg6xXaKZcbqNcdTAwMWNMSlx1MDAxMmBaXG6MXCI8/pa5QuJLXCI1QcL201x1MDAxMykwg5RkTt9TPI+SnCGMXHUwMDE2kEZ9zoxaIFx1MDAxYTWBujhKXCJd68+HRq1cdTAwMWW9r1Xl6fXZ+lx1MDAxZaB0+L5uXHUwMDBmSjx9SEsrXHUwMDBme1x1MDAwZTFtpLXhkJbUXHUwMDFhzGpcdTAwMDXS38ZcdTAwMTREYpFcdTAwMDSjwk8wjWJcdTAwMWH9zVxm/c2RYvGlb3llXHUwMDFj3YP06MboM87Xsc7IXHUwMDE2tVx0VEn7M1x1MDAxONhcdTAwMWOp0snFdvFjtzhcdTAwMTSvT1x1MDAxZi12frv9RIZHvztcdTAwMTPlVzX72+ypXHUwMDEz8np8IXeKO7un1cstcqZzmqStOPIwldMkbfdTSUFcdTAwMDRcdTAwMTQl/iRsqimRkptQXHUwMDExpKLAXHUwMDEzOFx1MDAwN8gzlPqOXCJI6Vx1MDAxMVx1MDAwM3xSYj2u26OyXGZAxVxuiddcZsFvwlxmXHUwMDA2v51FUuhcdTAwMTCNXHUwMDEzXHUwMDEy2LLDWJ17XHUwMDFibsXggc9cdTAwMTaAwtdcdTAwMTcpXHUwMDAwNUH1Rlx1MDAwMlDu9edcdTAwMTSASlx1MDAxNJXJXHUwMDAxKONcdIaDlf2uXGLh4dpcdTAwMWNcdTAwMTBNXHUwMDAw78AptbSuxHbuXHUwMDAxcfiu8lxutt5cdTAwMWZxe89QTiynmFx1MDAxOcODV1lSfVx1MDAxN87f3DjPNGJbg6gmVLuzX0j8JCBswm9cdTAwMTmdI0Xo3Z+0brc3NrXobl9stY9fXHUwMDFl3+GWpVaNi+NNiYdcdTAwMDL+XHUwMDE0XCIoXHUwMDE4XS+yxdy8KVx1MDAxOdxcdTAwMThEWCZcdTAwMThY8JJwZlx1MDAwNdGBd307U5iSX4Vi8CN15Pilcq0ky82VlfE6TGUlcFZcdTAwMWPaYpjmNFx1MDAxYSFcdTAwMTPYj0nqr4R6XHUwMDAy5Fwiioq5uVjchzlcdTAwMDWzXHUwMDEy2ENSXHUwMDAyq5doisnArK6v1GHpXHTCXHUwMDE0XHUwMDBlWmMqmFlcdTAwMWNcdTAwMThwgm1cdTAwMGaY1Vx1MDAxYZ6eXHIo/mXbqa+VTVx1MDAxNLzDTFx1MDAwNFx1MDAwYngwc5ZcdTAwMTmw+OxhwpDFL+Cctc9Yz4pPUlx1MDAxNoFcXE0gNO5QVXj5+XCrZHMxiVtpXHUwMDAx9o4yijKGJbxhbqW0xzF2KaWWNJCROlx1MDAxYZxoPb+lXHUwMDE4yDauXHUwMDE5NY5IlcB+dGBTwVx1MDAxYpTgwcT1JcRdXHUwMDEwf09cdTAwMGZxUNF4Z6O2XHUwMDEyLkUkRKR9N4s0cywk0ofXj297pXelzsRdYa9/yZh4zN5jaVx1MDAwMZpYxFx1MDAxZflcdTAwMWM+XoiAZVx1MDAxZVx1MDAxNCw13SGeNvBcZog0XHUwMDEyo5rGkVxylKrTKfVcYlxcRGvCkFx1MDAxOFx1MDAxOWHM2D30jFx1MDAxNZRiXHUwMDFjRlx1MDAxYTDrVLSh7a9q5lrARrJcdTAwMWNLb4BcdTAwMDZcdTAwMWJgmERGdzg261x1MDAwMZ5lqi3jrVNcdTAwMTKjgcBcdTAwMTdxVN5Y01x0uCGSjtrIarFcdTAwMDCdXX9Z01x0tyhIwUCNwlxcMIpcdTAwMDRcdTAwMDXUXHUwMDExIaFEVEWFXHUwMDA3v5aCXHUwMDEzt7bCwtiFm/L7h2mnj/TaiVxuPMmYM+VioLHlLZoojvXxOVx1MDAxM9DZXHUwMDFin1Z8Wbs4XHUwMDA0dFx1MDAwMutcdTAwMGJcdTAwMTNQ9/LzIaDJlbNJXHUwMDA0lDNcdTAwMTC5ilx1MDAxOa2pXHUwMDA1uUjDOZDKSJx9amlcXDW7JFx1MDAxZahcdTAwMTDKXHUwMDE5ldjMh4/M0lx1MDAxMahcdTAwMDV+XHUwMDA39f1cdTAwMWGMXHUwMDFiZuY+lO/PXHUwMDAyuV3LkFx1MDAxMqko5Sqml4WJn6bJXHUwMDE4zoqdazL5Pjk9lefv21x1MDAxZqWnzcOL5tvwovdQyO7G+/3jXHTG3i2Fp7myRP+EsSd9vFx1MDAxMFx1MDAwYplfcb0o+CZej1x1MDAxOc9cdTAwMWEmceKmYHg9lvaCv44jp3ZT+l38hZLUUKqwY4NcZpjVKyOXoMLeZnC7hCVUqGD3tlx1MDAwNFx1MDAwZSlcdFx1MDAwNymIQ1xcgMPwMe8tUkgrXHRVXHUwMDFh3lwipNKRu5KRNL9sssPK7i19eSrbfdtrKn17vurcMShcdTAwMDeGjYUs/Fx1MDAwZj2A2jH9Oc3+/N5swO+IMNQy0FxiXHUwMDA0zt+4ZYCjpTG9XHUwMDBiwKixU9Hvp8k5XGZAXHUwMDE4w3NcIlx1MDAwMtNcZkBIhuOky4/E4d/JXHUwMDFi4yk2NttSZt5pgqSY56hcdTAwMDe33klh7zCKjXpwXHUwMDE4/Fd5yEj1+NxcYiDnoYBkQKLAao1QI/Q1UFx1MDAxOahcdTAwMGZZ2juZqdB6eirEuDWaWWd+OI81d3BkPDZcdTAwMDPKe9DD7Fx1MDAwZfdmtdlpt1x1MDAxYivVV//rXHUwMDE2wOKZYGaELZ64XHLkY/M8KLZXb2zfXHUwMDE0xH67VHrvPlx1MDAxNldbNpXNU1x1MDAxMNpi4jpgWlxuiikwoSwlo5RcdTAwMDfmKsNcXG2weUa4/enszC2wXGYrqGLaXHUwMDFheNYuq8dcdTAwMDOSYKxcdTAwMTDcwonQNJD1vIS6XHUwMDBi6lx1MDAxYrMnNVhO0aPpLFx1MDAxMFx1MDAwM1JcdTAwMTMrXHUwMDAzXGJRVFk1XeNnt56eZFxyPTytkYuPzunu02nr7eVW3eyy4nGGfD9cdTAwMDNbXHUwMDE1PJBcdTAwMWb3u6yheCz4r0ZQXHUwMDEwoYy/wFpcdTAwMTCtw/Nm92b/XveGdv36pb+zXHUwMDFl8JytXHUwMDA0PeqYq0gwvEY4pjZETVx1MDAwNe5cdTAwMDHV/8zZSCby009enymhwH2QUtCbXHUwMDAyXHUwMDE4XHUwMDBiXHUwMDFlZ9pcYssk+lxuwrmaXG7oIahHXHUwMDAy3IW7p91cYlx1MDAwZjuRWfpVIbTkN5mF3mZ6fmONXHUwMDAxmFx07WpIXHUwMDE2n1BArSVA0YPjQPPJ12RYsDRVTtU3wVx1MDAxOfSq3ZXqsFpcdTAwMTnAhVf6w0WgOFx1MDAxM1hFmOLEbyFcdTAwMWaSY9vPp2+n7HA4uDvaur3cOKm/bFbTkVx1MDAxY1BcdTAwMTHYZlx1MDAxNHVcdTAwMWLhQHNCRbCWKo9p4idcdTAwMDRcdTAwMDFfXHUwMDFknZ8gx7HCWm2MXHUwMDAxI8ZKh/lcdTAwMDLiXHUwMDEzpCeYR9QwkO6SL/uUJsN9a2aOg0FKJaXgLjHAZXxtXHUwMDA3qDfLcVbm/DjOXHUwMDBiOd5cdTAwMTNFQldPau3u7qGp1Ml750/kOIV4MPifj8AgT5JcdTAwMTNmXHUwMDBludJcdTAwMWatmcRG8oJcbi1cdTAwMDNJ/SuB1lx1MDAxZUxKzS2hSuC5U9GTOTeu4z5QabiOsdL7KWIjXFyGsic1IVx1MDAxM7hcdTAwMGXF1F2QmvxrYMmS62RcdTAwMTZ+21x1MDAxOWLXUihOjXW3+ohv0Yx9alxmyTV5Mj+q81nmuSBEZ1x1MDAwMrNwXHUwMDEyXHUwMDFkx1x1MDAwNvKhOclcdTAwMDJrUo1qQXFMXG7iSnJcdTAwMTBTRIXyUj5cclSC4SzmxrZ0oFmrZSe0ZDjvpOUysbM6XHUwMDE5kZgq5M6f1LFzXHUwMDE4ONUgr9VcdTAwMWOHn5jahpLF8vr1zvmA2+7Nx/BtmHpSZ/2jdHfX29mqnFx1MDAwZWp3p8X+wWq7m3LyZeJ1k02V6a+bLFx1MDAxOVx1MDAxMq87idCBKGWcZspcIk2QXHUwMDFh7qeShlxySGM8RanCyUdcdTAwMDQrXHUwMDBmxyWGXCJcIlliKL8xmNVcZsRccohcdTAwMWPlmvXLl/IjWX7spqdcdTAwMDNcXIBcdTAwMGXGyLpLUND4VDacXHUwMDAxyPIuVGXwjdMlQn+zgYteTH1qwIU2XHUwMDBmXHUwMDBlMEHthjnA+LJzmsWyWlx1MDAxNne3zZ1er0yvLt4uaFE/rKbW/IZgw3XK1Ocsllx1MDAxMIyNVVx1MDAxZWb70ljFr1x1MDAxY+Xlcknjk3G7l1x1MDAxZbdovoM0dfdY5/GTljSO8J7nKO6z5/JcdTAwMDPhO4X+xmrj/WntomaGXHUwMDFivbRcbrPcXHUwMDFm3Dduerec765cdTAwMWWsXXZON+/aV/koTMO0XHUwMDE1wZD2TFxu073LXHUwMDE0XG7TMMBcdTAwMTlFy9nCmqxcdTAwMGVZz5bQXHRAY8qDo2CA7IEgx0rMKO6W/Z0m4G4/g77Ep6CCXHUwMDEzXG6CsIuNXHUwMDE1KIFFJXK6sYJcdGdYXHUwMDFhxVx1MDAwM32Np9CY8NxA0620XHUwMDA3/ZV667VcdTAwMDT3/p9Wq91fkFx1MDAxOSbJOiysRqN7WVx0bSVcdTAwMWbVWjpcdTAwMWbcmqP7J73KhX69Kcjn26u79KqVXHUwMDEyzy+RssIxy1x1MDAwNISSX1BjMS3KOctkqVqngPhBlupcdTAwMGUlUFx1MDAxY7vKi4WMzftcdTAwMTZYMGW4nqPxvHU8OCtcbvWx+sL22D15XudXO/VMOjCz/EhAhXs1aXQgx9HEoPtgOYLIQH3TJ1wiMJkyXHUwMDExXHUwMDExTHpcdTAwMDazXHUwMDAwudJKS22WOjA7QFx1MDAwZdNcdTAwMDNEUCz0I85weWBcXHd0XGK8YYIwm2+bw1xclOBXnHmlP/ynXHUwMDA1Kqg3aNZbtbDu+H1qcIK+XHSrwdFuVmI3k49cInzcXb99eC2dlfUr2yfnsvl42krvXTaU4aBzq6lcIpFcdTAwMGWIYNtcdTAwMTCgvpRbq+JsTIGDgeXPNE9Ha9OlXpxcdTAwMDD7o/SwZ1JcdTAwMTIzltBcdTAwMTCAvbbh346qXHUwMDFlmWLETpdcdTAwMDY8nV48aF1ebFfPXHUwMDA2V1x1MDAwN+U1tcPq5nG3maGtXHUwMDExiFx1MDAxNCVh2SaIien1ons1afSi5lx1MDAxZVx1MDAwN8NPgrCVQlx1MDAxMFx1MDAxM1x1MDAwMlxilclcdTAwMDBcdTAwMTHUw8ZhmojvXCKEKECWodVcdFx1MDAwMDnOQFx1MDAxYzFJXHUwMDFlQOIsXHUwMDBiVibeJ0ONXHUwMDExU1x1MDAwMiTPM1x1MDAxY1GLflRyXHUwMDExXHUwMDE04Fx1MDAwND3j7PqbeyS1s/PRIZfH/cvd7YvHzqmtdlonmYw+zGQh8Fx1MDAxZoeuk8RTiihcbvbD0p+aXHUwMDFidk8yYFcyIZVcZiZzXHUwMDA2rL6ozvtx7FhJuFx1MDAwMVx1MDAwMjM/7dbcYaXLgVwi3UtSPy2Wr4q28faS1qN617dlza8+nj42XHUwMDFmXHUwMDFme7cvtmj1Qk6Cdu8yjdZUXHUwMDE2XHUwMDFiP2lBLVx1MDAwZlx1MDAxNlx1MDAxZn1cdTAwMDJNyWSgYSs4wzlcdTAwMTdcXGIzXHUwMDE4u1x1MDAxMFOg/zDYnWaAnWWUoP5zOVuCbZzCRfZEalxuZy33Zlx1MDAxYTl0c1x1MDAxYpSb9f5cImjNXHRcdTAwMWEr0scttPB81GZysfUktcms8qQhXHUwMDE4XHUwMDFlwTBkZESjXHUwMDA1I1x1MDAxMSeqXG5DYiY/L1x1MDAxNedcdTAwMTRcYr7I4i1lQnClnFxittFcdTAwMWWNPzMvsLDX8HkmTW/SfpuUdu8qb+/tq4PiycFDU+7MIVx1MDAxNJl43eTKz8Trzlchu+9eXG6FXGbY9dBcbiWUUeBXQTP0q15CTsAwpogrpdBcdTAwMTPsW7JLpZxcdTAwMWTSl+khja1+XHUwMDE15c5cdTAwMDCIVbG9XHUwMDE3rSVKUEZyde/mopJLlVxufF9/ha78v8Vx6k5QjGHtnLCHfFx1MDAxNPXZ9muhODh4eVZnXHUwMDFiXHUwMDA3Q1HunFx1MDAxNJo0k6KGf6mk2NbBoajBwoV/lYRcdTAwMTeXYc3cUH2VXHUwMDFl1UxJXHUwMDEwnjGotrHOKVx1MDAxMNmg3eU83bdb189cdTAwMDel9WGp/L6nL2yz09zv8bNFSFx1MDAxOeIgjqTNK1xc6txlKn2qMWzCiFGaXGJtTVifgo2bXGY1ITwsWzUgXuNybJf6dFx1MDAwMvKus/iWJOGWulx1MDAwYlx1MDAwYikxsVx1MDAxMVOhtVx1MDAwNtKUt2c4+zGO1ahskTTqXHUwMDA0XHJcdTAwMTanUVx1MDAxZHvIR6Ou9Xb6rXq7rkivsflinkmr1nUgPFajXCLMOeCYY2ldWKMqQpjfQ0nGT4hbatTsuL5Jj2tlLHyjcFx1MDAwZoMjNDZcIkqJVJzMNVNo93qobzd7pz1e/7hUw429yvYh/22qz72aNKpPc09KTVxm9jjWNqi5vkKielx1MDAwMia4XHUwMDA3XHUwMDE0XHUwMDE1PshcZmGaXHUwMDBiZ1PVJUSSIXKbwZTUfmayc1RcdTAwMDelTMdBXHUwMDA0aFx0k2pcdTAwMTE1n+d5i6DrJuiWsK5cdTAwMWJbdT7arVFcdTAwMWXI1uC4Vlh7oCcv18dmq3Kosmk3opVcdTAwMTaaO1x1MDAxY7uKSO7B81eaxzt2PY5eXG5DXfx1qecmgLiUXHUwMDAxxIQzXHUwMDEyk++H0ex4+ipwXmpgXHUwMDE25S/Xc1x1MDAxZmc3/dezvVe7dta7uTgpq+7N5f1v03Pu1aQy8YynNONWXHUwMDE4g5HMQPORrzCmmYBcdTAwMGXBPcYpZsVSjVxyQpdqLjNCyulcdTAwMTFCgdJcdTAwMTmKSstcdTAwMDVcdTAwMTFcdTAwMTbfI5NcdTAwMGIpKDDIfFtI5WnhXHUwMDFkLpKFN0HnxFl4jj3ko1x1MDAwMzfs0cv63klzs7Tf6Xb37vpcdTAwMDfre6lcdTAwMDZ+SEk8XCJcdTAwMTkgXFxKbHRcdTAwMWJqIcVcdTAwMTn1/J5CXHUwMDE2XHUwMDEwXHUwMDBlUjQ6clx1MDAwZVx1MDAwNLJcdTAwMDdcdTAwMTT3p0FcdTAwMWN1zJybf5104jhfXHUwMDE2+v3vxHYlXHUwMDBitlx1MDAwNSgxRrizYUpS+1slpZ+7mSe2/XiIXHUwMDEygVx1MDAxMNhcdTAwMTTYXvXH4Xa67ftBZU710v12J1x1MDAwZdVjK1x1MDAwZkM4bqn54Ld3X9l4e1x1MDAxZr5+sIvtI37XfHt/rT+nwS/D+XFcdTAwMDJcdTAwMDde+qI2XFzExbnyXGbjWklcdTAwMTDwNpD5+aOfifaAOVkhQZVjXHUwMDBiTIeCXsI3XHUwMDE2vvdcdTAwMTngK1x1MDAxOZb5ups7XHUwMDA2Z/ZFOlx1MDAxY/iet+nyXHUwMDEz8lxmyUfQ+9X8eaFhXHUwMDFiWWNO9Sbn99X+2c3Ny8bOVks/vfWbj1x1MDAwNzpccl6FtZ7ERFx1MDAxNUNcdTAwMDTBZIRcdTAwMTBgteFcdTAwMWVcdTAwMTFcdTAwMTI9ekC3XHUwMDAz87d+9K1iaK9cdTAwMWFcdTAwMWMhapR2Tdha4jVcdTAwMTav1VxmeMXOXCKU8Gi6PO7JxqpbpUBRg0DOu7hMKNDgU03G+cbrYbX/1u4+r5xfRyfQLyB0k5abXHUwMDBmioetnfvto423XHUwMDEzXnm7erXbd+9n8jy154hSQz2plWCfrqNcdTAwMTCSXHUwMDA1155cdTAwMDWyZkA146TFqOrVzIvpM1xiQkJcdTAwMGIw4rj4XHUwMDFjIz66dC6A/o9cdTAwMDf/Z1x1MDAxYTAvkl38mFx1MDAxZcxcdTAwMDBkwi3c7Jj2Qlx0WVx1MDAwN0IpXHUwMDFjZThccpq/XsjqOtI35WG1UD06kf3revG19rCvu2mzXHUwMDBlNre39p529lx1MDAwN+fnq8OLo5vjp0Jhd2/8W6bKXHUwMDBlPDqoPZ9cdTAwMWO2O53Ntfvt95NK7fW6X0933a8/xbdbwrpJI/NydTnvXlxuzUxcdTAwMTlcdTAwMTVcdTAwMWUx1irs9MqYXG7BWWtPY1x1MDAxNVx1MDAxYmNcdTAwMTJcdTAwMDd4O5m0VMpPUbFcdTAwMDJUgCPoOX9X11x1MDAxZlx1MDAwM+Z6XHUwMDA2zYzRTtDAMb3CopUzP2BcdTAwMTbWXHUwMDAwlrXMOVnfwOlVs1x1MDAxNX6X0Vx1MDAxOfnZfbO7XHUwMDEwXHUwMDFlrlx0ujGsrGPWn4+eTlx1MDAxNmrJw9e1h2PXQZVSy4hcZoNaXHUwMDE59H5RTZGPO3S0YMZcdTAwMDOR4MNcdTAwMTmLu12ZXGbYXHUwMDFhmvtcdTAwMWTKMMmJXHUwMDA0e14t/dkuqD+5oZ6lQzrcb2Rezlx1MDAxNEJcdTAwMWXbXHUwMDE5xVx1MDAwMlx1MDAxYuNApOdcdTAwMThcdTAwMDfaPWxcdTAwMGXPqttXRTE8Lt9sV47qp7dP2ZQj/D+Zyk7PtT96PFx1MDAxMvyPRzAwul5ki7nNgNm4O3vY+KhrXlCH75JcdTAwMTWvqD5cYpYsjFxyXHUwMDFkp0ZRgLi1XHUwMDEyXGZnXHUwMDFkmFxy/jMv0khiYO1WYcd0XHUwMDFlPXypXHUwMDFhsyfLzcCaiMeBTkhQSZpcIjtVVEXWJD0hgLpcdTAwMWFcdTAwMGXLwZHlJrKm+TVmd5/kXHUwMDE0xMpcdTAwMTLh4aBOhekuWpHQiD0hjSfAXHUwMDE2XHUwMDEyWCupXcSKSo/iXGZcdTAwMWGgYJ9pootArP4wmfucnl5JrjRYrM5cdTAwMDLkeNlcblx1MDAxZlCCS0Hz9XsoTjC8PFx1MDAxM7fqwSoqOLylPP60flx1MDAxZrmaQGhcIlx1MDAwNZExXHUwMDFiyIddJZt2SexcblST8pjRjFq/bsqGxkuB3vJAgXGOXHUwMDAzarSJOjNBXHUwMDFl/4ze8lvnRJFNPSGlhmvj/CpBXHUwMDAyKn9cdHRcdTAwMTfQXHUwMDFiM5Mrylxmtlx1MDAwMHU0kfSXXHUwMDE431uSXHUwMDFhbiiYx3SOJVx1MDAxYXf7XHUwMDFksdqR7faT2a7cX5dcdTAwMGaOb2+LmflVpu6rv4ZfXHUwMDE1YrGAP1x1MDAxMVx1MDAxNMyDXqWmMlx1MDAwNVx1MDAxY5JrXHUwMDA19lx1MDAxNFx1MDAxMUJYJKxRfiU9PFdcdTAwMDSTXuFw8Z/B0lx1MDAxOflVas5cdTAwMDf8SmDuraCYNMapiNIr7llQc9hcco5cIkGBw/BcdTAwMWL5lfskp+FX2NFFW6mtUMyokIkrXHTcXGKwl7Sxylx1MDAwNtvwjPxWQN41Wsdawy1wRpSW9GqC1G1mcEVjoo00zlx1MDAxOcbxUWAlXGbT0tKcR/xxwpRgM7Grr4kxXHUwMDBiw60m0Fx1MDAxOWeLpsjy82FWu7RQPL9vbFx1MDAxZeyU7sXJeW/4SEw7S3yJYO4kI58zV0PxJVBcdTAwMDcodS3HXHUwMDA2hU6H9DK+9PnbaUHdTlx1MDAwZmpq0XlcdTAwMDBmrDO5g0XB/o1rIzEzh003unM6ynR02/l42i3IQuNwWGW95+bH3odKXHUwMDFiXHUwMDA3MqdcdTAwMTXePn4rs93+XHUwMDAzU7W3u4vt/Z3xb5kqvrTZUyfk9fhC7lx1MDAxNHd2T6uXW+RMr6a7blxuipdjfMl991KoaThcdTAwMDV+Q22BKexcdTAwMDJLh8bxrKj0OFx1MDAwMf6kQVx1MDAwNVx1MDAxM4cv2nJP+LM8NXbstmwh3CB/XGaYO+nBbLRcdTAwMDJcdTAwMDOIOFx1MDAwN9NRZmM7yVhBgNra6UaMJ0aXXHUwMDA0XHUwMDFjjll0dPk7efE1Judjzjp6gmKMXHUwMDA0l5zLz0dHJ1x1MDAwYrTk2Fx1MDAxMpg9nGmmOVx1MDAxOJDChom3VVx1MDAxZXZvN5ZcdTAwMGLCRbQ0QnD53TbYoaKJR6wwXHUwMDA056pcdTAwMWKFXHItlsPqJiD8xY3wLJ5cdTAwMGaD9ivRzr6p8IhjnZ9cdTAwMTJccidGpmPn02nxRvtU3DQvjs+23ktv6mnz8XTQXHUwMDFi/omOXHUwMDBmXHUwMDE3XGb8XHUwMDBmRlx1MDAwMTC6VGR3c1x1MDAxOLmbLLRWgp5cdTAwMDdcdTAwMTTaQuK0XYrFozzwpu9gk5LcYo9cdTAwMWFCudNcdTAwMTkyP8eD+ySlYDSWcHhy2EuWc39wUEhcdTAwMDBcdTAwMWFcdTAwMTCAUimLXkGtXZ5cdTAwMDcgRGC5XHUwMDEw6VdAL7tCTyH0ulx1MDAxOWxcdTAwMTRcdTAwMDI2n5FUOz1cdTAwMGbxgVx1MDAxZMaYwqFQuVx1MDAwN3bUtPInXHUwMDFh2ME3Llx1MDAwMrGZwCbiXHUwMDAzO+NcdTAwMWLIKW0m0aZKXHUwMDBl7CjrXHRCtFx1MDAwNuZieDhkqzj3cNRcdTAwMDX6XHUwMDFktFx1MDAxMa64jvFwnrI0XHUwMDE0xJuydHS6XHUwMDAyXHUwMDA0R2BcdTAwMDFcdTAwMTEzXHUwMDAwfVx1MDAwNWaRXU7TnID13uxcdTAwMDSHgCFcZprJ3Skk2OUgLFx1MDAwNLCXmiHBNpq/PnXm9npDrO6VnteKa5tcdTAwMDdd+UhcdTAwMWVPXHUwMDBm/kSGU4iHg/+yXHUwMDAzXHR5Mp2Z+VxmLJBij1TJXHSIXHUwMDAyyShcdMSmvlxiXHIlXHUwMDFlyFx0XHUwMDAxnFx1MDAwNvZcdTAwMDGszZGqkopkrVx1MDAxZb2vVeXp9dn6nlB6+L5uXHUwMDBmStxNsiiFxehPiiVcdTAwMDOFiKM1/UZS5T68qUiV8KglOGdcbvQuXHL3VFOMe36GsZExXt9lOGd2SdvPwKo48HxcdTAwMTZjM1x1MDAwNpyOkVi5JiBcdTAwMDfIdIVcdTAwMDVJrFxuLixmYlWLXHUwMDE20JlAY+ZcdTAwMTnQab9cdTAwMWWUr3dVcevuuFk7a1fW75ut3Siq41x1MDAwMjqMKE/h2Fs4MCDpw1x1MDAwM1x1MDAxNznFXHUwMDEwOdiLXHUwMDE0az6jyP5t8Zz/Q27gQVx1MDAwNmRjS8vxjLZcdTAwMDBZUjK+4Fx1MDAxZdVcdTAwMWZcdTAwMDd1mHMzXHKmXHUwMDE5KLssJYBcdEe5efI8eN46v14/ON3ePn6929pcdTAwMWM8pu9cdEhBwXjAWVxmnGNcdTAwMTGNTeKkRG4tZtJyI1x1MDAxN+ko/1x1MDAxZlx0Tb5mMftcdTAwMTWRUlpnX3zgTrGtzyizklx1MDAxOPjgXHUwMDFjvZpX6mP1st6o1Nmbbm731OF6/z71kG72UWhU1NUjf9XsoXp3SU+u1m/Hv2Wq2OR24/TkpHN8crz6cjpcdTAwMTT9ZrlweHuf7rpcdTAwMTH2/ksx7b57KUgnaCbpSVxycDOCYsJUyOC3VnhMXHUwMDBiXHUwMDEwiELqQKeRUZcn6WnDuSDYRohcdTAwMDTbRC1jk5PR/JZcdTAwMWXNflx1MDAxN0N4XHUwMDEyzlxihZKxpW8gqjHxjuTax9B3c2NcdTAwMWLLWehmXHUwMDFk2SHwN5xcdTAwMTK6QPVvXHUwMDEz9GOYdSbuXCJcdTAwMWbymdxQPMmdJ/RnXHUwMDA1hmCWcNDW4/NcdTAwMWKlZdTTliuwp0HzXHUwMDA2Tc5vfHOgpqCNucbotjHUXHUwMDAxb4Nmqd9kk1x1MDAxMMqXWdrJiH+f3ZWnsFx1MDAxYr5cci4m6MrT8f58eEhcdTAwMDbs1TnOu3nev3lcdTAwMWWer69f1zpPdVBLW1x1MDAxN7dbtczzY/RUzS9y9eTF4lx1MDAwMH9cbiFcYowuXHUwMDE22V9uPrzk2Vx1MDAxZCvjPjzfMVx1MDAwN1Ymx1xuuEAvkZVcdTAwMWaHXHUwMDE59aTvNrXUgFx1MDAxOFx1MDAwMFx1MDAxNlx1MDAxZTl9qZx4tdWyuLtt7vR6ZXp18XZBi/rBuSriYatCarTvu6CBovuVQI42XHUwMDEzWLMpXHUwMDE5zoyQXHUwMDBlQMzNqec+xyn4lVA4zVx1MDAwZr3v1lx1MDAwMLtcbjSPQYFcIqRUXHUwMDFlXHUwMDE38Fwi+vS4y6nHjCd48jTA5WD5XHQy9yM9y6JcdTAwMDa4rsGqJYdwVbF9Zlxyt0Jxa3ImWTNIv/FI6UqlXHUwMDA00MWL/9P6akm5XGJUa1x1MDAwMrFxXHUwMDA3TVx1MDAxM/eSXHUwMDBm4Uq2J5NcYpfVylx1MDAwM7BcdTAwMGKce2yjdXFcdTAwMTbEXHUwMDAxXHUwMDE2tFJiXHUwMDE54N1BuNBTmJBcdTAwMTZBPcNB18A/wlx1MDAwMumXbPRcdTAwMDVL7DuwXyQz8y1cdTAwMGVcdTAwMTjEXHUwMDA28i62peNnkVGjQIyAVTxHJ8rBzdt1o7axcXve2ehdrN/sbdTeWDanhNbB+Wq/i25RMEwsgEBcdTAwMTlJwNTkcoxvXHUwMDAxeim8aoVmglNMXHUwMDE1m3Q9J6o+L1x1MDAxNcZTnuQte6pZsvE5RqCUIFx1MDAxNmd4oNPFKlx1MDAxYU01o8QjiktrQCgpzqXUv5FBuY9mXG5cdTAwMDZFXHRnSL0plVx1MDAxODqjJFwiUuGocOzDXHUwMDA2P0ZFXHUwMDFinVrpMSsxXHUwMDFkV+JhcrUpXoZFJ0hRmp5BaVwipFHKTaDiXHUwMDFiNMFcdTAwMDNcdTAwMDKSr/KdpoxcdTAwMDJNjLWLmoJBVYfVylx1MDAwMFO1+sNF4ExcdTAwMTO4SZgzOVefXHUwMDBmS0r2jidnmVx1MDAxOeExTKBX1Fx1MDAxMlx1MDAxM8Tk53BcdTAwMDGm4KtcdTAwMTXwaVx0XHUwMDE2XCKlLppkPMqxr1xikHVcdTAwMTOXZ2YtxbRcdTAwMTRmQY8oXHUwMDEzaHG+RLlcdTAwMGLlbGauRDFrXHUwMDEw77TbS01ifVNY58jh9TlmmdHd58KLeC5cdTAwMTbKhWr7qNKs18v1LFlmIFtAzFx1MDAwNYo/fluWmbZcdTAwMDAmXHUwMDAy2pFbdCYwOvZxXHUwMDBm+Fx1MDAxMzBYQzSxQHBcdTAwMTibeL14cPkvXHUwMDEzj1x1MDAxMsPAyGFcZuCJTVro6JKzk6aZqVx1MDAxMabVXHRcdTAwMTD6XHUwMDE05FxuXHUwMDBlKNDEXHUwMDA0ON9cdTAwMTc50khcdTAwMWGAXHUwMDExXG5JOKHafDvtYlx1MDAxY17RXHUwMDA1z4U1uc9oKtYkgDVRjk9cdTAwMTFcZlx1MDAxOCnDXHUwMDEyloKEXHUwMDA1XHUwMDFiXHUwMDE1IKeVoNFEXi08IExcdTAwMTbuzuf0lpHMWbKmr5VNlKc8PWui2MJcdTAwMDH9XHUwMDA2rli9im9Si9mZIFdzXHUwMDFlUsa0XHUwMDE0era2ll/JWIvBmSYwlZhEsvxcdTAwMTnTXd+WNb/6ePrYfHzs3b7YotXlVIzJWO6hXHUwMDFimaDrKNxxWlFcIj1g3IqCpOeuoYPaMLB/SdyoXW1cYvr/rdYoXHUwMDEwzDKIN1x1MDAwMdhcIktcdTAwMTJcdTAwMGVcYlgrtHD2XHUwMDA3MPE1xThVx1xiMV03+elcdTAwMTjR2/mFvji1+9fH18PT5vVqsUjPK9midYxcdTAwMWHGg9j4LYxo7N3YRVBbJVx1MDAwMFx1MDAwM5qCiOVcdTAwMTO9RVx1MDAwNSdcXPK+VkEp4WlBhLXSXHUwMDAwZbaTiZlcdTAwMTLSw1x1MDAxMKON1EwmXezXUTLR2fnokMvj/uXu9sVj59RWO61cdTAwMTO3t1xuVs1cYqaiMDDosL5z5Fx1MDAwYvjhZEGW9lBcdTAwMWaO1jb2bD3YI5A6abFzplx1MDAxMFKFXHUwMDFjelEymjGI2TTHx1x1MDAxNzvHpaOnyv2mfvnoXGZkeTOOaFx1MDAwMs9cdTAwMDTarbmUsDNqXHUwMDFknTzTbFxurlx1MDAwNJZcdTAwMTPGjonExp9ajTUqxV1rojSIaKPQQ1x1MDAwYiw/YZf58dMxTeev/qxamyBlvuUkXHUwMDFikzD+mkK7XHUwMDBlXHUwMDAxNVx1MDAxMV5joPjcReRq8VibdOmRjPs7eVM8xaamX8ZMO0xcdTAwMTBcdTAwMDFjO4yIXHUwMDAzf1x1MDAxNzu9s061Ui81XCKSXHUwMDE51lx1MDAxMXkxgeO4lUhcbpNcdTAwMDUsXHUwMDBliZOoXHUwMDE4/CtBM46Hylx1MDAxNaPYXG5cdTAwMGZsWIWnP5qJXGJcdTAwMWbH7nJcdTAwMWNtPuOeRrc0WCbwXHUwMDFhmcHNq+FcdTAwMDBcblx1MDAxZS1cdTAwMWPE+1x1MDAxY2+vWIYjXHUwMDEyzHSDxydcdTAwMDXKZyspXHUwMDFllEEhLojBMsFQiFx1MDAwNMaja8/HYPk4q2y3X3dZca9zLtbt25VYf0g1U0NcdTAwMTHrgUaUfuU52KehxEOqNYDZXHUwMDEwzVx1MDAxOXGlXHUwMDE1a5CSlFrsf66VdNosy1lXsTBW6WFcZs9GS2x77oAxj5+rXGZoXHUwMDAz5mNFvrPpsHJcdTAwMDauamdcbtec9dvdqlx1MDAxM8OLMt4qtMKc5sCuXW/fko1cdTAwMTfKhsPbu/tr0tpsfKRBKtiYnlx1MDAwNIBir1x1MDAwZVx1MDAwMKpcblx1MDAxNfRcdTAwMThhPU5cco66XHUwMDAy8lx1MDAxOUWq0Z5k2nJcdTAwMDNv0ZpcdTAwMGJHftpcdTAwMTKpsUjV6ZFKKTZbXHUwMDAx3udyJIBWjYMqqFuGg2BzXHUwMDBlrH6O/abBs5q5N9lcdTAwMWYwiu5cdTAwMTfNn7t837s7rnWvblx1MDAwN1x1MDAxZGWqQ3vFyEk/XHJauaBY5O3HyoxcdTAwMTI81HnHSO2BXGLVXFwzJ1pB7WKfaWVcYjxFdy7EXHUwMDEyrbFoNenRKsDIo4Q53fk6mks20qtcdTAwMWEnXFyR3PXq7NPYwUrsL7ZeXHKtMFx1MDAxZqTuPJVKZ63y+/rNxs7rzunRll5vpi/8LjCmPSzq9qOkmElcdTAwMThWr/C60cZq6rZql6XfOeDWZtKyTCrscORcdTAwMDAuXHL21FxiIVx1MDAxNz6F2YjTlcwmXHUwMDAwl1I4XCJZXGbbhLPcfNpYt3eX4vistHdkuH69Jzp95XeBoe6x2JqEcWCL0bMsiWeN5SzGqFvWfs94jovpz7G06Ei27qxlxWOzljU8PMmlmGPUqa4/upXV6nWxe18u1T+OzjdvOvUseTi5YsS9mlx1MDAxNMxcZkS99ODYK/RqOOFhXHT2TjNcdTAwMWNcdTAwMWWLs8uH8Pv3mMXohvjHgGI1g3C3QIsxiOLMTtMsvnJSgIqmiuSdZ4HOas5mclt2q812v7poJdTJiiZM3Fx1MDAxMvaQUz3P887t8dvjRbOjOru8sbp1qflDXHUwMDE01K5cdTAwMDJqwzxuKZXuMaJM4jeDXU5ccubERUFt0M+pOcHAXHUwMDA1g9PnKunBYVx1MDAwZlxue8ZqgdMtzTL7XCJcdTAwMTnxa1x1MDAxOaJcdTAwMTQ4XHUwMDFjRzHpXHUwMDA0vDGxw1x1MDAxOSg8bspMcDz4r9eDb5dPr1ulV1O/I8WSoFvPN0+N7NlcdTAwMTe/v3hnPPtcInK6J328XHUwMDEwi5lJ14vcofw6JqqDe3XRuzwqMrn//HJQPFxcPW+5XHUwMDEzXHUwMDFkXHUwMDE4VuZcdTAwMTOt/YaJctRcInUlY5ZcdTAwMDNH/oZ1PcKgO0eP3Vx1MDAwNI9IMFSIIFx1MDAwNshcdTAwMWPcXHUwMDBlXHUwMDFluVx0vyrnXHUwMDAxOIwm1HBOXHUwMDA1lVpoSyNcdTAwMWJknvCxg0Fuf3Zpmlxy01BcdTAwMDLE2H4xO42BgWzVZ2vapFx1MDAxNI85JT9ET1EwSFx1MDAxZf7O8GtJXHUwMDA01ClcdTAwMDZSXHUwMDEwUCG4R6iKU1VcdTAwMDJr/Kmlwko4lcJRU6GN5+Key/LyXHT6aD1cdTAwMGJcdTAwMDM1XHUwMDAwmpj6iID7J+LDXHUwMDA3i1tJlatX8FthzFReXvmWrv+04lx1MDAxZITzrpBKZnth9lx1MDAxOb+FzOTzX1/K569Sp3PmX+tb4v71Wq++rcZ7Jv71JVx1MDAwZVx1MDAxMFxiVb/o5t//+vf/XHUwMDAyk+RsRyJ9RPCsubmit proven txbasic requestverificationverify tx proofquery stateinflight stateproxied queryverify stateinflight transactionsinflight batchesbatch builderselectbatchprovenbatchblock builderselect blockcommit blockmempool eventsuser executed txuser proven txUserfilter out invalidnotesexecute txconsuming notesprovesubmitaccount 1 + notesaccount 2 + notes...account N + notesBlock producermempoolNetwork TX builderbatch proversselected batchproven batchblock proverselected blockproven batchinternal tx proversselect candidateaccountexecuted txproven txsubmit txStorebuilderstateremote tx proverscommittedstate \ No newline at end of file diff --git a/versioned_docs/version-0.12 (stable)/miden-node/img/operator_architecture.svg b/versioned_docs/version-0.12 (stable)/miden-node/img/operator_architecture.svg new file mode 100644 index 0000000..961c58f --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-node/img/operator_architecture.svg @@ -0,0 +1,4 @@ + + +eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nO1dWXPiSNZ971/hqHlcdTAwMWRycl/mzftWhfFu19dcdTAwMTNcdTAwMGVcZlx1MDAxOFPGgFmMYaL/+3cv2CBQSlxiUNluXHUwMDBmVHRHXHUwMDE1XHUwMDAykZLynHtu5l3++8fa2rd2r1H69u+1b6WXQr5aKTbz3W//xPefS81WpV6DQ3zw71a90yxcZj553243Wv/+17/yjVx1MDAwNlx1MDAxOX+LXHUwMDE06o/Db5aqpcdSrd2Cz/5cdTAwMWb8e23tv4P/w5FKXHUwMDExv9882eLHpYuGeTgo9be3M/eb2YfTwVdcdTAwMDdcdTAwMWZ6XHUwMDFiULNUaOdr5WppfOhcdTAwMDXezzDtJIH/qFx1MDAxNdRcdTAwMTiljVx1MDAxYVx1MDAxZO/Bcam0IEI4J5zWXFxYOzrarVx1MDAxNNv3eD1MXHUwMDEzpfn4a/elSvm+XHJHmLGEwlx1MDAxMfr2cqPPXGZH8u81Onqn1W7WXHUwMDFmSpv1ar2Jw/1cdTAwMDcr4Z/xYG/zhYdys96pXHUwMDE1R59pN/O1ViPfhLsz/txdpVo9bfdcdTAwMDZnh7tcZnfz29RvXFy+XHUwMDBlnE29XHUwMDFm9S340fJ9rdRqTXyn3shcdTAwMTcqbbxFjI6vXHUwMDAyR9jYL1x1MDAwZVx1MDAxZdZ/xmNq5lx1MDAxZkv7+LRqnWp19HalVizhM/h2q+tcdTAwMTM/Vyu+/tzbo1x1MDAxZT9H8frOX+PBl0p4ZiaUMfCA5Pgmj+ecXHUwMDEwYvrdbL02mH+cXHUwMDFhTqk0xozH1dqCSddcdTAwMWWc9i5fbZXGXHUwMDBmXHUwMDAxx7Y9PSGDk3JizrVLL+MnXHUwMDEzmLInt1vC3j5eZJ9cdTAwMGV2X07V9/Wd68fut9Hn/nr92/hcdTAwMDZ2XHUwMDFhxfxwPMxI5oQ0XHUwMDFh5ipcdTAwMWZcdTAwMWSvVmpcdTAwMGbTd7daLzyML+GPwE2bXHUwMDAykH80IVx1MDAwME1cXMxcdTAwMWJ2XHUwMDE0gMNauHlcbuDB6Fx1MDAxNHZcZkBDUi4jscOEIE4xa1x1MDAxZFx1MDAxNZZa5WRcdTAwMThFnK5QXHUwMDEzgZqGXHUwMDFmNVx1MDAxM1x1MDAxZn+FXHUwMDA3Z4YzYcM4wKFYXHUwMDE2hVx1MDAwZabhXHUwMDE5Sk2tW1x1MDAwNFx1MDAxZGlO4PF8xHlcYpe//dIuNWv56lqr1HyuXHUwMDE0Sq3A06zX2qeV/vBcdTAwMDIm3t3JP1aqePvVxPnWq5Uy3olvXHUwMDA1XHUwMDE4eqn5LXg72lx1MDAxNbBJo1x1MDAwZtzW2+1XyzT4QFx1MDAwMU6ar9RKzf0kxqjerJQrMOaz2deQ77TrJ6XW8CrazU4peK9KeyNcdTAwMWJDuIrB9vHlRrnzcNzd46Xb2l3m9rZePXxcbmNcdTAwMWJPOY1tITWxVnEntaHM6fHMXHUwMDE5YJsqTpy2kjLO4WHaMXLfwG1cdDdCXG7jsYzaOVwiXHUwMDFjXHJazVSw/Y+7wesjce2Wh/VTclhcdTAwMGJONXNKy+CMfYO1iTR6TDFBXHUwMDA1oDB9WFx1MDAwM4/L4FRNXG7rRr0ybVPHf1tcdTAwMWJPi8E/Rn//zz+9n86E5lx1MDAxZb7Gs278/ZCpreZb7c3642OlXHJcdTAwMTeWw0GFbn0732xvwPOq1MrTx0q1YsSRwbfWm816976UXHUwMDBmPX34XuSxRr3aK1x1MDAwZp7oLEtOj1x1MDAwZa73W81f27ubL3m6TW/PnvaP5pDCXHUwMDAwZsJB6irDfJBcdTAwMDdcdTAwMWJCmFx1MDAwNDttzVx1MDAwMPIhxDtBrFDOXHUwMDBieW5cdTAwMTlx0nFmjXT4XHUwMDFhz5KvXHUwMDAy/TRMejM59lx1MDAwMcKaWSetXHUwMDBm/IJF23SUXHUwMDAxzoo0XHUwMDE173D+5XPPlVx1MDAxYvuw3ji5/9H5dXNeOm5vNCdcdTAwMDXw21x1MDAxY8zjbFx1MDAwZkjef/rPO0tJ7/063toq0/Of33uV5+7Zy0OeuufZp1x1MDAxZH75PPOYr5+fZLv9851dtpHJ5V86dqnhXHUwMDBlz3uXzb6U+4W9evbC6MPczcND76iXwnl/Xl6f7Gycnfeu1Pn9WVlU2J14SuG8p9ubXHUwMDE3/Kad/fm0337eqj0/PuV6xVx1MDAxNM57eLBrMzcv5e7R5Vx1MDAwZlHKnJ/x+4ZK4byslz/IXHUwMDE1lap2vre2u7aYK7bLuymct7qXPT2612ePnZPDrWJnc6/Lm1x070PIjExZRnCBJNdiXHUwMDFly1x1MDAxOMPz/lmfyGNcdTAwMTPA0dKBs1xmLFx1MDAwZTyvpime83iKN2BZmXNcdTAwMWOYXFxcdTAwMWJlXHUwMDAynsOI6VXKXHUwMDBl21x1MDAxN2H2VnJml4pR7uCPh9i5iiZ2qlx1MDAxNXPglC9E7DGqXHUwMDBlXFxAI9VcdTAwMWNzN+Ssfa/ni3/WbvPVPFxmNWDjXHUwMDAynlrAz0/FU2vXXHUwMDAz7vGUmzZDKE27aTj6tfDg03HR4o3mXHUwMDE040yh2UhBlGRK+n00blxyUdRILVwi0CxcdTAwMDR8XHUwMDAw/Fx1MDAwN8GGq5TjXHQ3QrMgwlxiXHUwMDAzQlx1MDAwM1jB4Fx1MDAwZo1/46ugO1x1MDAwNZet7Vx1MDAwN3d4/ZK/vlx1MDAxM1q/5Fx1MDAxY1SZVkpcdTAwMDdn9CvmlYvEvIOnXHUwMDBm1oWNn0pcdTAwMWGQfzVXXHUwMDBiQT5VRy5yguIrXHUwMDEznpvj84UscoqOXeCxvu5OJKGVXHUwMDAxsVx1MDAxNTp4ayhRYD6lXHUwMDE11mmp4Ops4EPlfFx1MDAwM8FLXGbnWo2d1b8mx5nUlfzWblamnL1S9bbeTSQ0XFy++Ou+dcrV5pbKdFx1MDAxZjNCybN6mJtcIlx1MDAxZEqmXHUwMDE4kdRcdTAwMWHBvVx1MDAwZSVz4FByq5SRxu9QSkKn1olGxGRcdTAwMWPhUtNcdG9zxUgjRurM4UhS6lx1MDAxY9xjOf7VgCPp1PS7I+7RWlvu2EKLSH4hPsvfu/l+eLXJvqv129rDXcm52nnN8Ll0uJYgcsfzcClcdTAwMWTuXHUwMDFmTVwiXHUwMDFkXHUwMDBlfEZcdTAwMDBcdTAwMTFcZu+ftDyswyWoXHUwMDA2pqlcdTAwMTTIXHUwMDE0YWRIVFx1MDAxNfDUwJF33K/DV4urPlxcPM+BXHUwMDBiMC5gdaz0LbDA3Y/U4WCTwbuyMl1cdTAwMWS+wNxccunwk9zmu4jvx0qxXHUwMDE4NFx1MDAwN1P6e4ZdmdbfXHUwMDEzo05HdW9lq4V18ySK6q6S3c11nsBmy+SWXHI8aLA+1IBLzz2GTShHmJDgOFk/fFx1MDAxOYdcdTAwMGaoQGzA+Fx1MDAwNCP82lx1MDAxNX49+O0mx69yIFx1MDAxZoFCffCVzky/O3ajXHUwMDE1XHUwMDEzXHUwMDAymHd8XHUwMDA1v92u5Z5y1bx8PKg/XHUwMDFk3+V0+fqht3+ReFx1MDAxZDP7Yvt78uKqdl1cdTAwMTPlp++n99+Pa9lcdTAwMTTWw84vj8p5/tg+uDrf29+93C00Ly76KZw3W9y9VqJ7dFTey3V6h3Inw053UjhvvZwtXHUwMDE2XHUwMDFm9jKX94rnmmXZ67DL41x1MDAxNM7b7WbM9Xd6dbi/n1x1MDAxNUeZy7ueeDpIYV1QcaZcdTAwMTmGR8iUXCI5/LMoiVx1MDAxZVx1MDAwMVx1MDAxMVx1MDAwMb5cdTAwMTRcdTAwMDc5zVx1MDAxNafgmkzxmdFEx/GZNVx1MDAwNCxcdTAwMTQ4o1pcdTAwMGJcdTAwMDOUOFx1MDAwNtdXXlx1MDAxNkyBzl7mXHThYFJYwY1viUDwyM1eeGaOmlRDOHDmgluH3usyamRcdTAwMDOP/1nLNevFznstXHUwMDBizlAmM3TBtDJcdTAwMTlcXMFa+Fx1MDAwMtJcdTAwMTEp/f3ds/Vcct5061l1cFY6XHUwMDEzj7XaQXKRMnC/mUL3wvlUXG5u98JRaVxchJOxcr9cdTAwMTfFddBccpzpZnDUKHCTfcv9gkW634xcdTAwMTnqrLaLXHUwMDAxezGdsrVO2/3dbuGJ9ot7N+vHt7nb0+pcXP63dFpcdTAwMDVcdTAwMThsKXvnXHUwMDFmTVx1MDAxMntcdTAwMDf+tySOKW7B01x1MDAxNpZPXHUwMDAxg1uiXHJcdTAwMWPV3L9uXHUwMDBl3rdcdTAwMDLv2ygmXHKXRjBf3OJcblx1MDAxN1x1MDAxZVxciDlim1x1MDAxY9hcdTAwMTgupPXAgoWN4Fx1MDAxYiwsRlx1MDAxNmpcdTAwMTAj6Zm7YWiTcmq5iMVP4nzPsCrv4Hzv5GVh29W2Xurm8Ojmtnd/dVm6SVx1MDAxOHHsXGbhjGpHgTGDS4/DZTMtXHSLXHUwMDAzLnOUSOGsklx1MDAxNP6TaoXb4ZBm4lbNYc+M4NRK5l1O5jJk5d6AqyjnXFwzm/ZWloWpwE1wos5cdTAwMGLcZqNA2qVWu1ZqXHUwMDEzXHUwMDAwV6lGKvV3QXLMXHUwMDFl9sT4pzFcdTAwMWI/4HRA3MrXr/ZcdTAwMWF721x1MDAwZv1fxf7d1U++U6mo5OLUMfA4XHUwMDAxpVx1MDAxMeHFmFVcdTAwMDPqkkYjebWCtiCU9Vx1MDAxY1BcdTAwMDYpJJAvfVBWMibEkEsnXHJdLFx1MDAxMmUxadr4sStcdTAwMWXqz7/uMs3W6Wlm09Q2srmPXkJTL9tcdTAwMDUmLlxmP7t92t/pt8/7XHUwMDE3ZzzZeWOl9GDpiHFl05LS/ruXxCRbI4mxUisluFx1MDAwNp2kp4AsZ1x1MDAwMFmBk2lASzPBMH48XHUwMDEwXHUwMDAwsTLJsTg2yXFcZlrHgHHl3lxyXlx1MDAxM71cdTAwMTJcdTAwMGXe/SD2XCJdLVxyJtnCqZdcbig7bdebpXexwTPU9FxmMzhtmafGnY4pvixdXlxu2fm1l+uXbp9OSoe10+plMj1cco+DXGJcZsCQ4OBINVx1MDAwNV5M8oEnZsVrOGg4yUdpXCJcckZcdTAwMGYw3DZxcrVcdTAwMGY9XHUwMDFj0kz02uToVVZcbuGMNzSMRW9DS0ZccrOKpq2njaQs8JxcdTAwMTdcdTAwMDBvrnNbrVx1MDAxND61gp5cdTAwMWViSlx1MDAwYrrXXHUwMDFifVa6zD5v25dcdTAwMWWzN1uFi41MMqBcbsFcYnPGaInuLZteskKkgmhcdTAwMWV5vmGkXHUwMDFhQzDtZlx1MDAxMHJm/Wl5K6T6kOqSI1x1MDAxNWhQaHg4viUrYULW91xyqcZyaUE8pVx1MDAxZC9ihJWByPVcdTAwMDWQul9cdTAwMWImqH5qrIZcdTAwMDeZXHUwMDBlWnefLCvou9t24+nutP24ccp4dX2e7Vx1MDAxN62JdVx1MDAwMmnY5+IqxVxieFaMOeePflxcubiLQnY9OWRcdTAwMTmlnFHwX3za2PHI1SqBUfdUL1x1MDAxNuP1emBOXHUwMDBm90dJXbv6xf2jfX7sn1x1MDAxZVxc986vjlx1MDAxM3u4SySlvf4tTs/jPmBKnqj/Klx1MDAxM9lIJlx1MDAxNPia0imutGUhNavsXGbAXHUwMDAxXlx1MDAxNYhZXHT+qLIuXHUwMDE497cykbF425hjV0eAelHWu9lpdMyKXHUwMDEyzDP4o1x1MDAxNlxuykpz6oZs5E6+Uyi138VCzvBFZ1x1MDAxOKxpuzk98HSsZnyW6qx8JoX1ZDh3mnIqxFx1MDAxOKLDfVmMulx1MDAxNJZppVx1MDAxNVx1MDAxN2GJK6Qg1GLuolx1MDAxNGxcIsF5jF9iuYBcdTAwMTNcdTAwMDPEuaOW6lx1MDAxNZ49eN7043mOdCajpFx1MDAxMcoxb6ySilTCXHUwMDFjyFx1MDAxOWwqtWnDfJDPtFx1MDAxMMzTzWeKmqH4yoQn5/h8ISP8mfKZMpRcYm2wpojkXHUwMDE0Y1xigVNcdTAwMDNcdTAwMWZcdTAwMWJmNLHwXFx5t0Sm7P7L0XFr52kj89J6eX6q9i6LP34kXHUwMDEzXHUwMDE1hjFcdTAwMTRcdTAwMTVWWVxy16emIyM5JVx1MDAxMmYst8xLSZwr0PDOcqqU1iBcdTAwMTg9KZYrTeHjoK05XCIjXHLHwFRvXHUwMDFkXGZcdTAwMWHyxcfxU+CcKblgXHUwMDFkjNhccmelzFJcdTAwMWLOd1x1MDAwM8v899pznjnmdORFfLGKWfKCXHUwMDExyozBfFmAspmOh9ScUKqdZkxyzsZIXHUwMDFkp0uDQ1x1MDAxZlHfRlx1MDAxMMVcdTAwMTmmoVqnwNJcdTAwMDSXXVegXHUwMDFlgXp7eWFhtbMuWJAhkNTBXCKFXHUwMDA1Y1x1MDAwNlxmrmGBMlx1MDAwNl9LWISmJr4y4Vn591FcdTAwMTRcdTAwMTg+XHUwMDAwXHUwMDBmjCthuFx1MDAxYS9oj/RcdTAwMDTHXHUwMDBipoyCr8A0XHUwMDA1j91TXHUwMDFi9P1cdTAwMDTGdnvjeqPPb3LPNb6VLz02mvvbXHRcdTAwMDVcdTAwMDbWXFzR2lx1MDAxOO1A/LrAitKQlYRcdTAwMDFZaOBwxFx1MDAwNjqHR1x1MDAwZlLbwlRcdTAwMDRkOG08y4QrieFjo53kXHUwMDEyQ4IyZ456d9C5idRcdTAwMTjWYnRTMGkzJYlcdTAwMDFkxpbagyu9NOBZlpp/L5GRYNTpyFxm2egpd9b63jtSe8d71eNm/1b1w3iOriotwNHTXHUwMDAwSH/lXHUwMDAz8IyJ4ehORKVexJTSg+8qsGdgzlao9qB6f46ddUOlXHUwMDEz/lxuejq6Kq7EMtPGLrZfNzJjc639l+3zuaCbzcLPzMGR2OKKO73/XHUwMDBlhdhCXCLht5ai9l9lXCIrqrkkUoE2tNRRXHUwMDFkXFzXXHUwMDFiXHUwMDAwXHUwMDBlXHUwMDBmO4P7s1GFzTTBpFx1MDAwMZxcdTAwMGVcdTAwMTS8dbFcbmRcdTAwMTlcdTAwMGVpJtxcdTAwMGWSw41ZyzU8JG9cdTAwMDajltHlalFka1xyTzdVM5pGXHUwMDE16jeD9Fx1MDAxOZb/Z9isKFuatuksV/mP9cNSp3O1e7N72MlcdTAwMTaz3aeLebbNrSVCOoXBS9IwN1x1MDAxNenCOCOOSVxmKGU0qKxG2+bGXHUwMDEwv4vuNFx1MDAwMb6nuHWL32RcdTAwMWU8XHUwMDEzdChcZmapScbgt1x1MDAwM2Vq/6drzZsjP8zn6NAgKFx1MDAwNpJcdTAwMDdWhic897hcdTAwMTL0lCpOaerlan9X/df061x1MDAxYsxSXHUwMDA3zVx1MDAxZvZmm9Wvv7+Yo6fde7F3Vrc3nyMyQINcdTAwMTROSVx1MDAxZPivMpE6XHUwMDAwJ5tQQWFcdTAwMTKB5p5og/DKKXpcdTAwMDanSEmMUFx1MDAxNr6umeCWeZbxXHUwMDAz+U5/Mzop1nHF55355FwiuW5cdTAwMTDcXHUwMDE5ZbVcdTAwMGWlQk8qmGnfm1x1MDAxYYtcdTAwMDVRU49/pZJcdTAwMDW6zCwgXHUwMDFh9sBcdTAwMTT30XZXq721XHUwMDE2WPhS8c/aSW5zrdGsP4ND23yfXlx1MDAxNnFcdTAwMTVSZ1x1MDAxOPFpLeG5orWIXHUwMDBiSkdk6PXWy0a/dlcsZ1x1MDAwZmljU9ZcdTAwMWIv9/N0fWKKY1xyRiypwT1dn1x1MDAwNOWgQZh13FgjXHUwMDAyXHUwMDE59qvSXGJLI/80OfC1VYIqqn1da0R0JilcdTAwMDc3Qzn+ju75nr10ZbvRO3xcdTAwMTAnldr3w/7Oz9zVfG70vOnlMeDwjyaZoWSCXHUwMDE4jiVcdTAwMWG0tsHsstfdboe73W/N0MKwwIZPwjlcdTAwMGLaXHUwMDFkwGWCkdArNzpcdTAwMTZcdTAwMTVnc7jRXHUwMDFja/NqXHUwMDEzdpi/XHJcImhcImGBXHUwMDE1Q2C2pVx1MDAxY0OXRmlcdTAwMDRCyGfwoGdYlWmrNzHqdOxavE9cdTAwMTK/vY31fISw8EiGKdXjx/xaXHJcXKNcdTAwMDbmRjBcdTAwMGXzIFxmXs44MfBdXHUwMDA3/IlBsNIjcuFcdTAwMTeMXHUwMDAzw2hcdTAwMDVcdTAwMDVcdTAwMTHkWCCe5VN3ZyvmW/fzSFxcvTyir5IjmlOnMWg5nFGNclwipuK/tlx1MDAwMlx1MDAwM+1SLnZcdTAwMDJcdTAwMTRusL/DXCKITnVXO3pG4is8XHUwMDE3x+dcdTAwMGJcdTAwMTncuTe3ly/yXHUwMDFkeC8qXHUwMDAyLtGmebxcdTAwMTZcdTAwMGb+XCLBgoKCYVx1MDAxMy5upWJWJ1x1MDAxOMP7bZLHrzTMXCI3Q4DWXHUwMDAwJOCkY77UlDKRjGhF2aAwXGajY8SMyE1cblx1MDAwMlLfXG4nXHUwMDA05zRYRW+sTDglXG6DTy1cdTAwMTVcXJpgKZJcdTAwMTW5TZLbz6VcdTAwMDN5UIywiWYlgaxWXHUwMDFkk1x0YFx1MDAwNGhTo1PfU3dcdTAwMDYwM55WXHUwMDFmxXmg7ohQ3FxmdLR2kk9wnuRcdTAwMDSLLFx1MDAxOGNcdTAwMWR10s0k0MhZPzhcdTAwMWGa75+cQDUwPrVcdTAwMGUuXHUwMDFkSUBSvVx1MDAxOKHG50msTUQhgWyyIJkwhEo5oWk4rplcdTAwMTOlsCBcdTAwMTVcdTAwMTdOwVNTXHUwMDFmybDxO72z5SNcdTAwMTbhsNZb01pqmCxcdTAwMWFcdTAwMGVYRv01rY0gblAxffjinjgkpjThXHUwMDEyq+Y5LrkwX5RgzfJcdTAwMDSbX5pgse4rcGW4XHUwMDBi6MBcdTAwMDJEV69XXHUwMDE458SZSp1flWNzdUD7PfwqsOeItFRcdIkkYidcdTAwMTnRYMFU9JHh3oEon0mwXHUwMDE5hoGXjFx1MDAwM1GDKVx1MDAxM4aJiZSO0Hz/5Fx1MDAwNLugQo3feZ5UqLiealxyc8xcdTAwMTmmg+Gqb0MgwOzgvKpcdTAwMDHHayE+MqxzvVI9zOzwl+1GvXCzeXj+424z61x0XHUwMDAz81x1MDAxNmzA5ltOSVx1MDAwNWxcdPZhaoVcdTAwMTmMu1x1MDAwNsFcdTAwMWHTXHUwMDFiXHUwMDFkd6uNUlHx5jLlVbTPQp1p7C1cdTAwMTWTe96MO9RHOtxcdTAwMGVgQIXRvW+ExoJccilHpGBxXHUwMDE1R5cr2bD9kn9sVEtrlVx1MDAxYdwoeFx1MDAxOJ1Cu1x1MDAxM1EqSbzfhlJsVOesIafUaG89c0jN7f7l5nrzR7t+eSy08mSORG9cdTAwMTlcdTAwMTlJNFx1MDAwNo9p69syMiifxoD2rI2vyjksiOfSXHUwMDFjeFx1MDAwNvVcbrdfeveKrYhJXHUwMDA2c9ZcdTAwMTlcdE9vXHUwMDExQI9s2Fxcm0ZX51fdnev8Ra6g9lvH992d5+puYb7oXG6FXHUwMDAxXHUwMDE2c7BFXGY8/KNJZOqYttjMSoJNkzC9p4rygl9cdTAwMTm0dFx1MDAxZWAwXHUwMDA1joPDXHUwMDE0XHUwMDE17sDb+lx1MDAxZmkqm1x1MDAwMjDuklx1MDAwM4NcdTAwMGbLzEtcdTAwMWJcdTAwMTL+6Fx1MDAwZURnZFs4JrVx6e5cdTAwMTktMHVDhu5cZlx1MDAxNUu+0IbB/lnDQIPPXHUwMDExhTnDzEzbvtirSKnq2IbLle9cdTAwMWKFg1x1MDAwMq90tuV67eCEeXKTXCJcclx1MDAxZqVcdTAwMTZ+gEdcdTAwMTk+7Vx1MDAxYzGYVDnE91hIrVxm37L4Ls9h+FxmV0xST9ZcdTAwMDJcdTAwMGXFRMZKMINcdTAwMWRcZrBcdTAwMGLp+1x1MDAxOb7+82G2eXL6/JC9KZxtPVx1MDAxNzi/3/75YYbPP5okhs/hilx1MDAxN5VKYMFcdTAwMTLBQoqQXHUwMDE5wuOAoSmR2MSR+l28lc3zYeI+OSaMXHUwMDEyXHUwMDEyXHUwMDFkcJ9cdTAwMTY04e7qozhcdKAqxcUnNHlcdTAwMWL5duH+M1x1MDAxObtcdTAwMTmmJdQuXHTHv/Z7rNyx/dXrdfP32Xz1VjaOrnqVXHUwMDA3ypJbOSspwaWaSO9OXHUwMDEwzuJcIp9WRm5BQFfmMHJMYq1Mp31cIpZRXHUwMDE27d4pimuOPM0kgllWrlTJP12c1C7sfpHy7nWt8Vwic/bDrJx/NEmsnKVcdTAwMWFcdTAwMTfFI61cdTAwMWOnxMQhY2XlXHUwMDE2XHUwMDAwxa85QKFgciuqqU/52eglTEa5hS+yxeJkf6+dXHUwMDFiNlx1MDAwNvw8dm6GcfG3XHUwMDA1/D12bqvb3Wjv72RPzMX1IbN0/9fm2VlyO4dVj1xiY5wq5vfmjFx1MDAwNE1cdTAwMWKzL6FwndP6kMzk32Q791x1MDAwM9LnXHUwMDFlkm7nRqbPMVxmf1x1MDAwM/PndfBUeJc3sJ9rwINcdTAwMTc0RVx1MDAwN2/pxLF/xp33d/VcdTAwMWSu7mVPj+712WPn5HCr2Nnc6/JmMYXztphq7Vx1MDAxNHdfNjZcdTAwMWVcdTAwMWaL21x1MDAxN1V6/7hXTXbeWFxyMGzyYmVgj3o5XHLAevmDXFxRqWrne2u7a4u5Yru8mzA6xFxiSpxcdTAwMTC4PcWpZtNl8DAuSTDJmFx1MDAwMFx1MDAwMlx09IBcdTAwMTntZnJJhOZmkNArYDZ6ausyrGCPXUmVlcroQFDrSlx1MDAxOIyIpJqUSKJcdTAwMDPvpGNcbuid+9xixaNcdTAwMDNDwFx0cpTxxZo6/VxySmhFTlF8ZUKzc3y+XHUwMDEwij9TJS1KXHUwMDE4jFx1MDAxYjtDOFx1MDAwMcN3elx1MDAxY9W69lx1MDAxNnOhXGI3XGZ0m1aWXG6pw2F171x1MDAxN3Gxa+0+z262XHUwMDBlOrdN11CFcr32azORn8JcZmdEUCxcIqxcdTAwMDU8pOlcdTAwMTZcdTAwMTngm1x1MDAxMCqBpFx1MDAxY8VCpYFcdTAwMDCKUYywXHUwMDAy7YVcdTAwMDFullxyqnmKVfbScEgzWelxXHUwMDFlXHUwMDFm3qH65N7AX1x1MDAxMd63XHUwMDFkXHUwMDE13KcgfThLvZ1cclx1MDAwMJ0u5620XzJDqf/3qqWVZNjpuCzxwmtWVConhjvskO1LapLUYlx1MDAxZmdQuFx1MDAxYYuuepKapMbNZW2A4Fx1MDAxY1x1MDAxM8x6XHUwMDFjXHUwMDE37jThWLrYUudcdTAwMDR87ovGVqVcdTAwMTD3X1tefmCkgKLOpz5cdTAwMDDdoVJcXONSekpwoI3UXHUwMDBieFx1MDAxYaXkx6tcdTAwMGaGXHJPjZOOWzlcZnxcZn5dXHUwMDBiXHUwMDAyw+VcdTAwMDLUt9bUcTnrdNHTfnA0NOHTXHUwMDE0M78jLlx1MDAxNcRcdTAwMTcoXHUwMDE3I7GFlTOGL1x1MDAxOPlcdTAwMWa/eFx1MDAxMlx1MDAxOEKGXHUwMDEyreFJgCDkXGZ72/FwXHUwMDAxUvCIwCGXXHUwMDFh01x1MDAxMVxmxXanXHUwMDFmqJviO5jGcqxVnMBcdTAwMTTAXHUwMDFjOqXgLk/XQlAqPlKVXHUwMDEx9FXBOktuqaaBXHUwMDEwqnG0qiNcdTAwMGWblmnMaGEuIKw+NcHOKaRSXGL7by7Nr5piVTPqXVxy5uGe3G/0qo1cdTAwMDXN7NKsj4zrXHUwMDE31Fx1MDAxYWw49/H0XHUwMDFhNUfxlVx0Tc/3oMOtbLWwbp5EUd1Vsru5zlx1MDAxM/CijOBcImqEsIZyXHUwMDFj5SCQK0xGRFrsXHUwMDBmiMlcdTAwMWLo4NgwXHUwMDE5JWLI+H6mXHUwMDEzfiVcdTAwMTVcdTAwMWHzKFx1MDAxODdgSpzR4dQoSahcdTAwMDV/edCvJWJQ78eQ8cuKM1x1MDAxOFJcdTAwMTGjcL1cdTAwMWPbQEg2VVx1MDAxNlx1MDAwM3xJwsCjZNiFwJdcdTAwMWFFiXRot1x1MDAxZNpcbkYtXHUwMDE3Plx1MDAxNcotUSBGXHUwMDE0uulKikD7qVx1MDAxNUlOkGR7eVx1MDAxMao0aFBhmbdcdTAwMTidil5Mp0I5zlx1MDAxNUvRXHUwMDBiXHUwMDFk0+RCXHUwMDFkkVOlyZiJiq/wXHUwMDE0/VRESVx0SDUgSCGAlFx1MDAxNFx1MDAwNVx1MDAxMZ0k/z1d4UhcdHY401i2XGJ8XHUwMDE1LYVHOMrBXHUwMDA22keKxdhcboXxXHUwMDBlOWUge7EnODhPRunpQrsgXHUwMDEwXHSIXHUwMDBm50BcdTAwMTebYDGaUeUsRsDQ4nqjRi3PPGtsmIfqrEGZL1x1MDAxNKeB3JxcdTAwMTVcdTAwMTFOXHUwMDEw4fPyRFxik1VgN2CvXFyMXHRcdTAwMWK11kkrxFclwshZiq9MaIK+XHUwMDA3XHLOUVx1MDAwNlx1MDAwNJ4o+K1aweBpMFx1MDAxNiOgXHUwMDE2lVx1MDAwNlx1MDAxZIxcdTAwMTeHYLXh+ZGMXHUwMDE1kzPzoG4m1Vj8mdLAdFtcdTAwMWLvQTDMXHUwMDFlMWyQRlx1MDAwYvf3XHUwMDAz+VGVy13a0Y1cIn88t9dcdTAwMTeX1/Sg6MmFieRHKkDrSoF7ZW46jZ5zglkujltkT0+pUSpcYvOVXHUwMDE1pESnXvH/61Bhb2kqdEZyQ1x1MDAxZPcxYTByLVxcj1x1MDAwNFtKXHUwMDA1XHUwMDFmZUpcdTAwMWJcdTAwMTPgz4mPZ8Kp+YivzHAqpkl60Vx1MDAxYqDhb71cdTAwMGJcdTAwMDXk9N3uyU3ntnR1UN7e7fZE5XDnLjFcdTAwMDUwXCKpjaRcdTAwMDAqicRQyS9NXHUwMDAx81x1MDAxNlx1MDAxNU6BXHUwMDAz+inIIWe0kqBtfSRcdTAwMTCdJceU1Fx1MDAxOE3H096dWJHAR5LASbVcXOQ/mtnur9rhXHUwMDEx7Z10Ls7yx0lJgDqCScWUamq4tVNZsVxcgrfqNFx1MDAxN05orJ/5NUng3XWApcvrXHUwMDAwXHUwMDA2ytlcbn911fCy+tgjwrZcdTAwMTJ2sVx1MDAwNPJcdTAwMTVcdTAwMDN8Ulx1MDAwNmjcXFzWj93V7e3J0e5Gj/aeLzZ+eKKtI2WAZlxcYOt6KUMrJVxcaeKUdlxcaPB8lWdf7SswwPvLXHUwMDAwy5aXXHUwMDAxUlHJqPZcdTAwMDYpXGJcdTAwMWItXHUwMDAzgNZcdTAwMTW8+IpcdTAwMDS+XHUwMDEyXHRk3fFJQ1x1MDAxNZ9+tjbyRXq5aU4v+zyxXGbA2lx1MDAxOFx1MDAwMH+MkcZQsylcdTAwMTIwnDClmOPKYora1ySB95dcdTAwMDF8eVx1MDAwZXCOKuy56nNcdTAwMDVkTL6F1FRcdTAwMWGTcpH1XHUwMDE1XHUwMDA1fCxcdTAwMDX0e+r5olf9SVx1MDAxZlx1MDAxZlx1MDAwZU8vzEm2ITY9YclRWVdcdTAwMWF7/CqsZlx1MDAwN/9pRqd6/FxuLYKVXHUwMDAyVtnFqUUmWzFHZLJcdTAwMDEthkWJvVx1MDAxOVbR9Vx1MDAwMpiQWihcdTAwMTmstPnbk4srd3u75afT40r+7vy03OxsXHUwMDE1XHUwMDBl69dJXHUwMDEzlurlbLH4sJe5vFc81yzLXoddXHUwMDFlT/7KQolQ3W7GXFx/p1eH+/tZcZS5vOuJp4NcdTAwMTTOq162XHUwMDBiTFxcXHUwMDE4fnb7tL/Tb5/3L854XG7n/f2JW/PwdVxm+/ifdoh9PElcdTAwMTGaKYKKXHUwMDE0XHUwMDAziIxcdTAwMTFsumy6pcHaPVx1MDAxMbW5MOpSO2m55MxcdTAwMTPct8rg9lx1MDAxMY9MTjxW4611Xt6RMlx1MDAxNKIy4lx1MDAxZMyGsFLJlPdgwfHBUKdFpMZbSkS21O7Wm1x1MDAwZmtnL3/WNjqVavFzpHHPsOLTXHUwMDE5XHUwMDEysVeRTmpEPFx1MDAxNce6XHUwMDE2xlx1MDAwMrQtVdRcdTAwMGU1xXSvJsuJXHUwMDEy2ljMyFxmXHUwMDA2nI2grVx1MDAxY7GDiqOvL5+PwSUmVjhcdTAwMTCfXG7DaeTXXGbcTSHD26o5hFx1MDAwNipccmqd8tbii6nMbSXlTqVZmXuId2OCjXw+zLWImpCDb4en4vh8IWv8IUG6WGlKU8rBUaNWXHUwMDE5T+xcdTAwMTmR8MydktiuRHIpXHUwMDE3zGKIp7G1YNiFkjDZNOYmWLAxKjwmQZSxjH9kNNpBu3K5v311scvbx/r7zuWPVqvrSV3wqFx1MDAxYmP0XHUwMDA0XHUwMDA1htwqXHUwMDFlVDeeXHUwMDE1VjHG/ThcdTAwMGU3ZTnzhTjOzMFxyiqmWHBJK5hlXHUwMDFlxXFCUFxmXHUwMDAyc4vso05cZiOsxynMh4Xau7yJmsfSY6Ner65cck+dSo/WaumuXHUwMDFkI2dcdTAwMTZN7YxcdTAwMWNpOqIl3s+LXHUwMDE1LUBDXHUwMDAxxNLpXHUwMDFjbak40do4inwlXHUwMDAzNmnco1x1MDAwZXNcbjRcdTAwMWNcdTAwMDSW1TbQMG2ca2SIwO8jrU2Emq3gvDZcdGfrh/NcdTAwMWOLpMCuXHUwMDAwqkBXnGAyp4hcXCNVXHUwMDFhbDk3iyyRxoN84Ll8fDJnhnFDsHUt11x1MDAxYYthOT6hZXDrnzolXHUwMDFjeOWOazXzdJGzfnDUXHUwMDEw5oDdsLFcdTAwMDa1uOHwXHUwMDFlwiixXHUwMDA2XHUwMDAxYYSNX1x1MDAxOHiqg/VcdTAwMDPjfHlCXHUwMDAy+7ZhWydquHK/P1x1MDAxY1x1MDAxNcSkhLtcdEPCsu9GhoP0XHUwMDE5XHUwMDExcFRITDelXHUwMDFjV3M+UFwircvj5lnt0pby9rmw3n583tXZm2RcdTAwMTJJxUokqVm8RHKKUFxyL4Csk1hgeCWY5mBYl1xcMGmhnPR3a1x1MDAxMtFBJ1RTJfhiva5nUCny11KLQLXX5ZP2y2fWSt5RpqOT4tetZyzuyDjYcjXLs3G+5r30a2bSpIHUjaW1kMb0T8a9zeqjM1xuXHUwMDFkXHUwMDE1WGRgob2jXHUwMDE5XHUwMDBlj1x1MDAwMDXw8VqI0YncmVHlhugvuMkuv/RdsmtcdTAwMTLnPWdcdTAwMDZ1ooXBmrdMO4zf9GhcdTAwMDfJXHUwMDAwmoPm6ExcbmXDVVx1MDAxZNNe6GEwXHUwMDE2jtnWmPzorPNliI92j+HlqPvINZ+NRv/5+qbSyOmfrX7/6fKgdiN+JFx1MDAxMzTaxjKjXHUwMDE28cxoLPH2U1uJmChq3EwuYlxm+iCce1x1MDAxN31YuFx1MDAwZf+4+5K2VDK1UFx1MDAxZvNcdTAwMTksqLilS8mYwlx1MDAxYr38WVx1MDAwM3y0/a3UPoeWiVx1MDAxZWo6giZ+wzx+4Vx1MDAwN1x1MDAxY404P0TReNhK51uqVauKw1Go3Vo+XHUwMDAyjlPKXHUwMDA0N85fbT+62D6XXHUwMDA2i1x1MDAxNS9UKHRcdTAwMDaa562G+3s0TSYwXHUwMDFi8Vx1MDAwNfPwPTTKXFxrLljGQWtcdHpgkKdcdTAwMWSSXHUwMDAznIz22Vx1MDAxY3bU9lSvSr2kXHUwMDE2XGInM+g3hKGVNqybuCXvVlx1MDAxOOGP10f0Ld9onFx1MDAwZcjy7Vx1MDAxYb89V0rdjei4lD9ex4bEUFx1MDAxYWDqrz/++n9cdTAwMWFBYUUifQ==External servicesLoadbalancerRPCBlockProducerRPCrpc.testnet.miden.ioStorePublicInternalFaucetfaucet.testnet.miden.ioexplorer.testnet.miden.ioexplorerHorizontally scaledRPC providers...Example infrastructureTransactionproverBatchproverBlockprovertx-prover.testnet.miden.ioNetwork TxBuildermempool updatesnetwork txscommittedstate \ No newline at end of file diff --git a/versioned_docs/version-0.12 (stable)/miden-node/img/workspace_tree.svg b/versioned_docs/version-0.12 (stable)/miden-node/img/workspace_tree.svg new file mode 100644 index 0000000..016e69a --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-node/img/workspace_tree.svg @@ -0,0 +1,4 @@ + + +eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nO1cXFlT20hcdTAwMTd9z6+g+F5cdTAwMDdN78u8sS9J2MP2zVRKWMJcdTAwMTbYkpFlXGaZyn+f2zJYsjYvXGJCpaypmlwiUku+3brnnHu7b+vfT0tLy9FT113+a2nZfWzYbc9cdO3B8lx1MDAxZub8g1x1MDAxYva8wIdLJP53L+iHjbhlK4q6vb/+/NPudq3kLqtcdTAwMTF0hne6bbfj+lFcdTAwMGba/lx1MDAxZv69tPRv/H+44jnmfrt72nWf9qLPXHUwMDA3XHUwMDE33e1cdTAwMWLXsa/Do5X41rjRi0Gh24hsv9l2k0uPcH5FIGJJglx1MDAxOVx1MDAxMYpILiRcdTAwMWZdfoLLnFwibWlEXHUwMDE41UJcdTAwMTCq9OjqwHOiXHUwMDE2tMBEW5hcdTAwMGLC0fNBR01artdsRdBGJVx1MDAwZlx1MDAxZJrw11x1MDAxMlx1MDAxYZ3pRWFw565cdTAwMDftIDR2/lx1MDAwZrvmv8TKa7tx11xmg77vJG1u4iNpc+O12yfRU/xkXHUwMDE4Wlx1MDAxOMLlzPPPX8zNnC+7XHUwMDBifrDZ8t1eb+yeoGs3vMhcZoxOOmCM6+468Vx1MDAwYvonMSm0O+6ueUN+v91cdTAwMWWd9nzHNeO+fC22x37Nd55/bax5z3WdeJBcdTAwMTlnhFx1MDAxMiZGV1x1MDAxMoeCkc+e3Vx1MDAwZvzYubBcdTAwMTJaXHUwMDEzTVx1MDAwNUlcZuhtgEdF8WNv7HbPTVx1MDAwNtpcdTAwMTixmfW2tMeNOVTkPkajXHUwMDFlpPyxtXaI2ofR3e6mdKR/7j8+9Fx1MDAwNuvLo3Y//yh+7PDm3ire3rn8et/yzvd31lvisDPY/D7+Ky+/b4dhMEg99/mv5Fxy9LuOPewnllxmK6koZUgkY9X2/LvseLeDxl0yNJ9SXHUwMDA2Z1BX3MtcdTAwMWPqxlx1MDAwNmlcYjguscUoJZJcdTAwMTDCOVx1MDAxNjJcdTAwMDM4Olx1MDAwMXBCWFxcas2Q1FxcacVYXHUwMDFlb2SBt1x1MDAwMrztTI83RrhcdTAwMTZIcFZcdTAwMDA3cKIyuIGHUc2FXHUwMDEw86CtwnFcdKaCq1x1MDAxOVx1MDAxYzfxQ+N/0Pstu99wU554XHUwMDEz+NGJ98NcdTAwMThN0NjZLbvjtWNPXHUwMDFje8hq22ua3i83wF43XFxOXHUwMDBmQeSBao1cdTAwMWF0PMdJq0xcdTAwMDNcdTAwMWVqe75cdTAwMWLuTiNXQeg1Pd9un5ZcdTAwMThu96Pg2O1cck2Pwr6bXHUwMDFlXHUwMDE1d+dcdTAwMDVcdTAwMDHYXCK8XHUwMDAyvWxD76w621x1MDAxYmtXXHUwMDE3q963jZuHXtd1ptdMxaTFZIVmUlx1MDAwYlx1MDAxMflcdTAwMDLhXHUwMDA04Fx1MDAwYs18LYZ3Z9FMzSCwUWlPfYYwkzJ7dqSYWCosQG5cdTAwMTNb31xcMTtP97x/1du9WD9k+P76fHBPXHUwMDBlrqdVzKfdjfD7gX7avXSO+fnKw8n64PPOdIpZ+dzPbO389sfBXHUwMDBmPOh1dptHzYPv3/drU2JcIjRLXHTXq5S4ePSmUWKFhCVAiTVBXHUwMDAya0ayMKZcdTAwMTNgXHUwMDFjs4DWXG4xoVJcdTAwMWOwkOFKXGLvzVx1MDAwMGGqmYZgSOdcdTAwMTTX6DAhZSAmXGJhjbhIgb8mIUacMjyD3+aEeD9w3I8gw1x1MDAxM1x1MDAxNDArw+Nm1yPCZM+/v7tEO0H40FljX44j3e99nl6EIfq1XHUwMDE0XHUwMDEzZVwizDTktVIuXHUwMDEy1+H7rFx1MDAxM8Ffp0ewwlxmXHUwMDEzqlVR3kpUTptHKiwpRlx1MDAwMt7wXFyR9HwqfNE/vrv88rjd31xiwvVz/8z+7GytzaZqSEHsUI+qXHUwMDE1WzONqmlcbrJEQGIlZURcIkbGccExskhcdTAwMTUulLAo1lx1MDAxYV5cdTAwMDBcdTAwMDX1o0X5ZSo4WsBiXHUwMDA0i/3pYWFSXHUwMDA0znCRrOFU1JBFXHUwMDA1eJlmb1wiazM6bk7W1sz1v/3DMHAgX1x1MDAwYj+Cwk2Ql6zCxT1YynegXHUwMDFlrfNuKHnyVVdcdTAwMDTbh0fbrTZpfuHR9FqnkKzWOmaxxSTty5utXHUwMDEz1Fx1MDAwN9ODXHUwMDFhXHUwMDEyXHUwMDAxLDWnhVx1MDAxOSfT2bPJXHUwMDFjLcNSIPWeXHUwMDE5py02Ns5WNzna37389rDPXHUwMDFmoofByrRax4dcdTAwMTOXmqFZXCLhXG5wXHUwMDE0WzON1kmJLIpcdTAwMTRRkilGUWqObKh1ZFx1MDAwMi44s1x1MDAxMGRwXHUwMDEyU0yZXHUwMDE0XFzkYbFI4opgcTjDXFyq5EhcdCVcbkPAfGaXwEJTJECXalx1MDAxNLv5PDcndidRXHUwMDEwfogkboKqZCUuY3c9ynbUvjl19OGpaovrtvTPTu4vXHUwMDFjb4blR4EnZXGYk8VU6vCF1lx04ZNcdTAwMTnmYVx1MDAxMCZMYo5yMy5cdTAwMDbfvDyN44aVJUPvmMa198VX3DknXHUwMDBmq9tX67p73fLo9sWvXn5cdTAwMWNcdTAwMTJcdTAwMGZcdTAwMTFcdTAwMTjPsopTgbriXk4jmVx1MDAwMkMoqUEyqVwiXHUwMDA0a8HGXHUwMDAxXHUwMDA3llZcdTAwMDOOXHUwMDE5PtBaQFKvXHSXXG7n8baQzFwivJ1OjzeCzZw0YYXL/USX4o1IZlx1MDAxMEfZPHCr03Nzknl8uP5cdTAwMTFcdTAwMDRzglhlXHUwMDA1c8zqeuTye6SO7txcdTAwMGIsv+701vvnZ61cdTAwMWS3WVA3UL7yWDnpKbFKT+4s5LI++H6bQS6lVlx1MDAwMutCtaSkNFx1MDAxMdRcdTAwMDRIXHUwMDE1XHT8jnmg39JcdTAwMWR8dSld4ZydXHUwMDFkba1+OZBcdTAwMDK/w1xuYeVz9fWj5+3rrb67s9J4jC5a+LB/UoNcYtdeXHUwMDAzVDx604iwmc8h2Igwx1x1MDAxMpFUfDSEMZtcdTAwMDBjxkHDjVxiU6nAZchi8XFo0kRcdTAwMTSfzSDCiFxiKSFcdTAwMTIqnKVcdTAwMTWlXCKsiFx1MDAxNlxcgGTWJ8LDSVqIylJ8P4dcYn+LvHbvI8jwXHUwMDA0XHUwMDExzMpwxu56hPjpXCJyLlr8cnswOOvpq69cdTAwMDdcdTAwMWTpXHUwMDBlZshbKa8qm5VcdTAwMTBGL2Zk31x1MDAwNMLnM1xisSBEXGKBRKFcdTAwMTKr8rknjjRWXHUwMDE0v+fyo33q2WdcdTAwMGZcdTAwMTf3W61cdTAwMWbN1sr+/q1e5edTS+be3sn2/tFaXHUwMDBmXHUwMDFkiOPOce/cd1x1MDAwNqguyWSIXHRRU95a3MtpJJNrZWFcdTAwMDHhXHUwMDEzUvGSciZvhURl8lQv1VqqeFGTLup1nk2aiLeLXHUwMDE58EYoMaVQhZEvqVjYRFjDe8F6rtD3TTWzXHUwMDFiXHUwMDA2UfBcdTAwMTE0c4JeZTUzY3c9mon2NndcdTAwMGb3XHUwMDAy7Fx1MDAxZWxdXHUwMDEye3s7XFxVx1x1MDAwNUVcdGWaiVx1MDAxMYS9glGKcPFkL2BUJHNPXHTljErfTcmPZHp05DGMOejySFTHdLVcdTAwMTZMR6Ht97p2XGJv81fiXHUwMDFho9dcdTAwMDP7slx1MDAxONiJvLy8UPp85mdi/HNcdTAwMTmDkIAwwlxu4Y4q4E45RUrMXHUwMDA39ypcdTAwMWR8q2LYV6Skb6XbJc+dXHUwMDE0Zkgq2bq/vv/5dmflylx1MDAxYlxc0yC825xtXHUwMDFhXHUwMDFi1JPWXHUwMDE0XHUwMDBlXHUwMDE0WzNNOCBcdLU0uFx1MDAxZThcdTAwMTFcIkqgXGaVcMXT4UCeSnS8XHSHQl84gsa6aFx1MDAxN03NVU6/XHUwMDExdVxcTVx1MDAxZlx1MDAxM0gkgO5xcVx1MDAxNk1zXHUwMDFibJJcdTAwMTBcdTAwMWNkgs5cdTAwMTWAj1nxevfNhVx1MDAwNOtBp1x1MDAxYvjxj1x1MDAxNcVcdTAwMDWpQqpa4oLrIIqe94LGXHIyccFcdTAwMDRNzsZcdTAwMDVFxteUUFdyb4bHMmCWwlx1MDAxMoBjJmhcXIufaPawXHUwMDEwX2mLJ4X4eTBcdTAwMTNcdTAwMGJpXHLhI1x1MDAwNJ5cdTAwMTJcdTAwMWNcdTAwMDel4s9cdTAwMTGaXHUwMDA1s5hcdTAwMTSQ9LF4Y4iuOdb/jcBtT1x1MDAxYlx1MDAxN5CyuFx1MDAwMCOuQeJcdTAwMGJWqmJaLVx1MDAwNT3QNqTrhNde8jE35ruBl1xyO5K/llx1MDAxMpeJ/zH6+58/XG5br5T7qTlyXHUwMDFlmjwvp8ptu1x1MDAxN1x1MDAwMZQ7Xlx1MDAwNFx1MDAxZD00RuboN7LDaFxyXqvnN8ff3vOG8mn2XHUwMDAxxFx1MDAxY9bom1x1MDAwMVhBYLykRCqwXHUwMDBlIGqWKlLNmnY3poh06K3RqFx1MDAwZomLuL4z2apqUlx1MDAxYreKYlx1MDAxOFRcdTAwMGVcdTAwMTZx+G3Jkz2bI6tcYmRcdTAwMDR5VzXjs2rIqOXaOTiAlelrWdZy29fBYKpcYqd6kaSSXHUwMDE1zS5DjlVZtkQlT29wyLNcIjXbm4qWXHUwMDA2qFwiVt1zXHUwMDFkv1x1MDAxMf9dv5r/XHUwMDA0IIVQgou2XHUwMDBmY5lb1Fx1MDAxZsU8lEA8rJSaa7d+1TSIQlxcJKL6y/gv45HmwIK8hFx1MDAxNVV3XG4rtcPOXHUwMDFjQ1x1MDAxZv5QXGZcdFx1MDAwNMkxXHUwMDEzSnHGXHUwMDE1pLgqqTJMXGJyPj6sXjBcdTAwMTnnQ86wpphcbiW1RCxZPFx1MDAxY9nAoYmZpftVlFhdZFVJiZxcdTAwMTHge1x1MDAwMXQoITmQODNcdMzAv1BloKgspokgjGmMXHUwMDExXHUwMDA0m1x1MDAwNeyokJWaYkrXny6IcpwoXHUwMDFirydKriDiXHUwMDExxfPF5XFcItBcdTAwMWFEXHUwMDFmtE6efKlzYnSu6eKa48RSNzVHzkHfg1x1MDAwNas/2zDOQFgxIc1SjYnMmCR5XG7C2MIk6Vx1MDAwM9Lz0WJ1OddcdTAwMTg1Y4K4woxQKlx1MDAwNUNcYlx1MDAxN9FcIk1NXHUwMDFjXHUwMDE3xq7vRJDVs43VmbSCoFx1MDAxMFJcYlx1MDAwNNFcdTAwMDdcdTAwMDGmTKBcdTAwMWZcdTAwMTOkwMRikF4oXCJMNW+Cn2RZ2sLArVx1MDAxOJtpMaHSXHUwMDEwXHUwMDFiXHUwMDExpKZcdTAwMTalwjRjklx1MDAwMtEmv7EgyHGCdF6fSWNiUkVV/CWaiukz8F/JIdasnVwiP0YqXe6n5sh56Hsw5NQ5K6SskOczgrSKXHUwMDBifbnOk5FKgtu3XG5cdTAwMTWRZUrLTJlcdTAwMDPDWFxiSFhUzlxmQ1x1MDAxN8lqW1x1MDAxMU+/XHUwMDEzJZ45p97Z5lO4s3J8uXrFelxy70fE8pRo5iuzhTqIWbh8zZEyXj23yGnBKiNcdTAwMTHaWiTQZbTnvZr2pNlKqXjhXHUwMDA26XTpRob1XHUwMDA03CRY3dkzXHUwMDE1TPBcdTAwMGZcdTAwMTBcdTAwMTVigbJZ8FxuRtxKUrnye1NebFx1MDAwZSyoScTrpMUs7kuuzMRcdTAwMTdj17pB+6lcdTAwMTl7wMT4qXJVtTrBVNhcdTAwMTKQXFwwU4cgXHUwMDE5z3ycT0DgakhTUqmVLijMXHUwMDA13kZcZljGRO1cdTAwMWH4JuGTpEJcdTAwMDFeXHUwMDE51YhA7iMoQ5gv4qcyXCK5rWElwrA64YU7abAq/1xmmCn+o0ih3zR+Wil1VHPkXfRDXHUwMDA1UGbSXHUwMDFmca5cdTAwMThcdTAwMDJao9zsYkux4nPoXHUwMDAyqj9vXGJVXWqVMYRcdTAwMTPFhVx1MDAwNlx1MDAxZlx1MDAwM+lcdTAwMDHVyq8+cCtV5ftL88q9x6fDe9c5woNvT7eis7Fy2mdcdTAwMDV5ZdF+XHUwMDA1rKqWXCKw1tWFW+BJXHUwMDE2vCZcdFKlzTb799iw8Fx1MDAxYvFge5ZdXHUwMDBiXG6iXHUwMDE2iFiKXGKv/INLVCGlmZA17lx1MDAxY1x1MDAxY9ZcdTAwMGVTrlJlenNUW5xcdTAwMDfhXHUwMDFkvMaGu+S4XYCC6zeelqLQrWVcdTAwMDN+271JOUeu9FwiXG66ZXVcdTAwMTdjXchcdTAwMTZZTGFzPTVcdTAwMTfrnq+um3fdxplq3eCVPtm4dGb4rFxmx9LSXHUwMDE4fFx1MDAwNXFmcqTc5nuUzo1cdTAwMTa7XHTrK6pcdTAwMGVmQLSE6IVcdTAwMTZ+QS39XHUwMDFki1xmoM1GYKzQu374+1x1MDAxYu3cSXHTdE/Xd24vb69cdTAwMGY8XHUwMDFl/bJcdTAwMWTyxdZMI3bcXHUwMDA0+YhcdTAwMTJTpCEkXHUwMDExLFx1MDAwN1x1MDAwYj5cdTAwMDFcdTAwMTaYW3pCcaFcXMCiXHUwMDAwXHUwMDE23elhgcH5hSZUXHUwMDE2zVx1MDAxMVx1MDAxMFG6y5YgJDhGsv5N8kCE8lWFhftuNFx1MDAwMOX42z81oYvdiMDsv/21vtd2PsYn1SbITe6jocP+LKW6s5TrzcxC+OmZQpbtbvckgncwiuSXXHUwMDFmPHewVo6NT89cXGGw4caJ5M9PP/9cdTAwMDPDXHUwMDE1Zk4ifQ==FaucetNodeBlockProducerStoreRPCUtilsprotoComponentsWorkspace dependency treeNetworkTransactionBuilder \ No newline at end of file diff --git a/versioned_docs/version-0.12 (stable)/miden-node/index.md b/versioned_docs/version-0.12 (stable)/miden-node/index.md new file mode 100644 index 0000000..b53f7a7 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-node/index.md @@ -0,0 +1,25 @@ +--- +sidebar_position: 0 +--- + +# Introduction + +Welcome to the Miden node documentation. + +This book provides two separate guides aimed at node operators and developers looking to contribute to the node +respectively. Each guide is standalone, but developers should also read through the operator guide as it provides some +additional context. + +At present, the Miden node is the central hub responsible for receiving user transactions and forming them into new +blocks for a Miden network. As Miden decentralizes, the node will morph into the official reference implementation(s) of +the various components required by a fully p2p network. + +Each Miden network therefore has exactly one node receiving transactions and creating blocks. The node provides a gRPC +interface for users, dApps, wallets and other clients to submit transactions and query the state. + +## Feedback + +Please report any issues, ask questions or leave feedback in the node repository +[here](https://github.com/0xMiden/miden-node/issues/new/choose). + +This includes outdated, misleading, incorrect or just plain confusing information :) diff --git a/versioned_docs/version-0.12 (stable)/miden-node/operator/_category_.yml b/versioned_docs/version-0.12 (stable)/miden-node/operator/_category_.yml new file mode 100644 index 0000000..397a6a7 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-node/operator/_category_.yml @@ -0,0 +1,4 @@ +label: "Node Operator Guide" +# Determines where this documentation section appears relative to other sections in the parent folder +position: 1 +collapsed: true diff --git a/versioned_docs/version-0.12 (stable)/miden-node/operator/architecture.md b/versioned_docs/version-0.12 (stable)/miden-node/operator/architecture.md new file mode 100644 index 0000000..9bc9ac0 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-node/operator/architecture.md @@ -0,0 +1,55 @@ +--- +title: "Architecture" +sidebar_position: 2 +--- + +# Node architecture + +The node itself consists of four distributed components: store, block-producer, network transaction builder, and RPC. + +The components can be run on separate instances when optimised for performance, but can also be run as a single process +for convenience. The exception to this is the network transaction builder which can currently only be run as part of +the single process. At the moment both of Miden's public networks (testnet and devnet) are operating in single process +mode. + +The inter-component communication is done using a gRPC API which is assumed trusted. In other words this _must not_ be +public. External communication is handled by the RPC component with a separate external-only gRPC API. + +[![node architecture](../img/operator_architecture.svg)](../img/operator_architecture.svg) + +## RPC + +The RPC component provides a public gRPC API with which users can submit transactions and query chain state. Queries are +validated and then proxied to the store. Similarly, transaction proofs are verified before submitting them to the +block-producer. This takes a non-trivial amount of load off the block-producer. + +This is the _only_ external facing component and it essentially acts as a shielding proxy that prevents bad requests +from impacting block production. + +It can be trivially scaled horizontally e.g. with a load-balancer in front as shown above. + +## Store + +The store is responsible for persisting the chain state. It is effectively a database which holds the current state of +the chain, wrapped in a gRPC interface which allows querying this state and submitting new blocks. + +It expects that this gRPC interface is _only_ accessible internally i.e. there is an implicit assumption of trust. + +## Block-producer + +The block-producer is responsible for aggregating received transactions into blocks and submitting them to the store. + +Transactions are placed in a mempool and are periodically sampled to form batches of transactions. These batches are +proved, and then periodically aggregated into a block. This block is then proved and committed to the store. + +Proof generation in production is typically outsourced to a remote machine with appropriate resources. For convenience, +it is also possible to perform proving in-process. This is useful when running a local node for test purposes. + +## Network transaction builder + +The network transaction builder monitors the mempool for network notes, and creates transactions consuming these. +We call these network transactions and at present this is the only entity that is allowed to create such transactions. +This restriction is will be lifted in the future, but for now this component _must_ be enabled to have support for +network transactions. + +The mempool is monitored via a gRPC event stream served by the block-producer. diff --git a/versioned_docs/version-0.12 (stable)/miden-node/operator/index.md b/versioned_docs/version-0.12 (stable)/miden-node/operator/index.md new file mode 100644 index 0000000..72dc099 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-node/operator/index.md @@ -0,0 +1,7 @@ +# Operator Guide + +Welcome to the `Miden` node operator guide which should cover everything you need to successfully run and maintain a +Miden node. + +You can report any issues, ask questions or leave feedback at our project repo +[here](https://github.com/0xMiden/miden-node/issues/new/choose). diff --git a/versioned_docs/version-0.12 (stable)/miden-node/operator/installation.md b/versioned_docs/version-0.12 (stable)/miden-node/operator/installation.md new file mode 100644 index 0000000..1f27c63 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-node/operator/installation.md @@ -0,0 +1,80 @@ +--- +sidebar_position: 3 +--- + +# Installation + +We provide Debian packages for official releases for the node software. Alternatively, it also can be installed from source on most systems using the Rust package manager `cargo`. + +## Debian package + +Official Debian packages are available under our [releases](https://github.com/0xMiden/miden-node/releases) page. +Both `amd64` and `arm64` packages are available. + +Note that the packages include a `systemd` service which is disabled by default. + +To install, download the desired releases `.deb` package and checksum files. Install using + +```sh +sudo dpkg -i $package_name.deb +``` + +You can (and should) verify the checksum prior to installation using a SHA256 utility. This differs from platform to platform, but on most linux distros: + +```sh +sha256sum --check $checksum_file.deb.checksum +``` + +can be used so long as the checksum file and the package file are in the same folder. + +## Install using `cargo` + +Install Rust version **1.89** or greater using the official Rust installation +[instructions](https://www.rust-lang.org/tools/install). + +Depending on the platform, you may need to install additional libraries. For example, on Ubuntu 22.04 the following +command ensures that all required libraries are installed. + +```sh +sudo apt install llvm clang bindgen pkg-config libssl-dev libsqlite3-dev +``` + +Install the latest node binary: + +```sh +cargo install miden-node --locked +``` + +This will install the latest official version of the node. You can install a specific version `x.y.z` using + +```sh +cargo install miden-node --locked --version x.y.z +``` + +You can also use `cargo` to compile the node from the source code if for some reason you need a specific git revision. +Note that since these aren't official releases we cannot provide much support for any issues you run into, so consider +this for advanced use only. The incantation is a little different as you'll be targeting our repo instead: + +```sh +# Install from a specific branch +cargo install --locked --git https://github.com/0xMiden/miden-node miden-node --branch + +# Install a specific tag +cargo install --locked --git https://github.com/0xMiden/miden-node miden-node --tag + +# Install a specific git revision +cargo install --locked --git https://github.com/0xMiden/miden-node miden-node --rev +``` + +More information on the various `cargo install` options can be found +[here](https://doc.rust-lang.org/cargo/commands/cargo-install.html#install-options). + +## Updating + +:::warning +We currently have no backwards compatibility guarantees. This means updating your node is destructive - your +existing chain will not work with the new version. This will change as our protocol and database schema mature and +settle. +::: + +Updating the node to a new version is as simply as re-running the install process and repeating the [bootstrapping](./usage#bootstrapping) instructions. diff --git a/versioned_docs/version-0.12 (stable)/miden-node/operator/monitoring.md b/versioned_docs/version-0.12 (stable)/miden-node/operator/monitoring.md new file mode 100644 index 0000000..9e3ba94 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-node/operator/monitoring.md @@ -0,0 +1,287 @@ +--- +title: "Monitoring" +sidebar_position: 5 +--- + +# Monitoring & telemetry + +We provide logging to `stdout` and an optional [OpenTelemetry](https://opentelemetry.io/) exporter for our traces. + +OpenTelemetry exporting can be enabled by specifying `--enable-otel` via the command-line or the +`MIDEN_NODE_ENABLE_OTEL` environment variable when operating the node. + +We do _not_ export OpenTelemetry logs or metrics. Our end goal is to derive these based off of our tracing information. +This approach is known as [wide-events](https://isburmistrov.substack.com/p/all-you-need-is-wide-events-not-metrics), +[structured logs](https://newrelic.com/blog/how-to-relic/structured-logging), and +[Observibility 2.0](https://www.honeycomb.io/blog/time-to-version-observability-signs-point-to-yes). + +What we're exporting are `traces` which consist of `spans` (covering a period of time), and `events` (something happened +at a specific instance in time). These are extremely useful to debug distributed systems - even though `miden` is still +centralized, the `node` components are distributed. + +OpenTelemetry provides a +[Span Metrics Converter](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/connector/spanmetricsconnector) +which can be used to convert our traces into more conventional metrics. + +## What gets traced + +We assign a unique trace (aka root span) to each RPC request, batch build, and block build process. + +
+ +Span and attribute naming is unstable and should not be relied upon. This also means changes here will not be considered +breaking, however we will do our best to document them. + +
+ +### RPC request/response + +Not yet implemented. + +### Block building + +This trace covers the building, proving and submission of a block. + +
+ Span tree + +```sh +block_builder.build_block +┝━ block_builder.select_block +│ ┝━ mempool.lock +│ ┕━ mempool.select_block +┝━ block_builder.get_block_inputs +│ ┝━ block_builder.summarize_batches +│ ┕━ store.client.get_block_inputs +│ ┕━ store.rpc/GetBlockInputs +│ ┕━ store.server.get_block_inputs +│ ┝━ validate_nullifiers +│ ┝━ read_account_ids +│ ┝━ validate_note_commitments +│ ┝━ select_block_header_by_block_num +│ ┝━ select_note_inclusion_proofs +│ ┕━ select_block_headers +┝━ block_builder.prove_block +│ ┝━ execute_program +│ ┕━ block_builder.simulate_proving +┝━ block_builder.inject_failure +┕━ block_builder.commit_block + ┝━ store.client.apply_block + │ ┕━ store.rpc/ApplyBlock + │ ┕━ store.server.apply_block + │ ┕━ apply_block + │ ┝━ select_block_header_by_block_num + │ ┕━ update_in_memory_structs + ┝━ mempool.lock + ┕━ mempool.commit_block + ┕━ mempool.revert_expired_transactions + ┕━ mempool.revert_transactions +``` + +
+ +### Batch building + +This trace covers the building and proving of a batch. + +
+ Span tree + +```sh +batch_builder.build_batch +┝━ batch_builder.wait_for_available_worker +┝━ batch_builder.select_batch +│ ┝━ mempool.lock +│ ┕━ mempool.select_batch +┝━ batch_builder.get_batch_inputs +│ ┕━ store.client.get_batch_inputs +┝━ batch_builder.propose_batch +┝━ batch_builder.prove_batch +┝━ batch_builder.inject_failure +┕━ batch_builder.commit_batch + ┝━ mempool.lock + ┕━ mempool.commit_batch +``` + +
+ +## Verbosity + +We log important spans and events at `info` level or higher, which is also the default log level. + +Changing this level should rarely be required - let us know if you're missing information that should be at `info`. + +The available log levels are `trace`, `debug`, `info` (default), `warn`, `error` which can be configured using the +`RUST_LOG` environment variable e.g. + +```sh +export RUST_LOG=debug +``` + +The verbosity can also be specified by component (when running them as a single process): + +```sh +export RUST_LOG=warn,block-producer=debug,rpc=error +``` + +The above would set the general level to `warn`, and the `block-producer` and `rpc` components would be overridden to +`debug` and `error` respectively. Though as mentioned, it should be unusual to do this. + +## Configuration + +The OpenTelemetry trace exporter is enabled by adding the `--enable-otel` flag to the node's start command: + +```sh +miden-node bundled start --enable-otel +``` + +The exporter can be configured using environment variables as specified in the official +[documents](httpthes://opentelemetry.io/docs/specs/otel/protocol/exporter/). + +
+Not all options are fully supported. We are limited to what the Rust OpenTelemetry implementation supports. If you have any problems please open an issue and we'll do our best to resolve it. + +Note: we only support gRPC as the export protocol. + +
+ +#### Example: Honeycomb configuration + +This is based off Honeycomb's OpenTelemetry +[setup guide](https://docs.honeycomb.io/send-data/opentelemetry/#using-the-honeycomb-opentelemetry-endpoint). + +```sh +OTEL_EXPORTER_OTLP_ENDPOINT=https://api.honeycomb.io:443 \ +OTEL_EXPORTER_OTLP_HEADERS="x-honeycomb-team=your-api-key" \ +miden-node bundled start --enable-otel +``` + +### Honeycomb queries, triggers and board examples + +#### Example Queries + +Here are some useful Honeycomb queries to help monitor your Miden node: + +**Block building performance**: + +```honeycomb +VISUALIZE +HEATMAP(duration_ms) AVG(duration_ms) +WHERE +name = "block_builder.build_block" +GROUP BY block.number +ORDER BY block.number DESC +LIMIT 100 +``` + +**Batch processing latency**: + +```honeycomb +VISUALIZE +HEATMAP(duration_ms) AVG(duration_ms) P95(duration_ms) +WHERE +name = "batch_builder.build_batch" +GROUP BY batch.id +LIMIT 100 +``` + +**Block proving failures**: + +```honeycomb +VISUALIZE +COUNT +WHERE +name = "block_builder.build_block" +AND status = "error" +CALCULATE RATE +``` + +**Transaction volume by block**: + +```honeycomb +VISUALIZE +MAX(transactions.count) +WHERE +name = "block_builder.build_block" +GROUP BY block.number +ORDER BY block.number DESC +LIMIT 100 +``` + +**RPC request rate by endpoint**: + +```honeycomb +VISUALIZE +COUNT +WHERE +name contains "rpc" +GROUP BY name +``` + +**RPC latency by endpoint**: + +```honeycomb +VISUALIZE +AVG(duration_ms) P95(duration_ms) +WHERE +name contains "rpc" +GROUP BY name +``` + +**RPC errors by status code**: + +```honeycomb +VISUALIZE +COUNT +WHERE +name contains "rpc" +GROUP BY status_code +``` + +#### Example Triggers + +Create triggers in Honeycomb to alert you when important thresholds are crossed: + +**Slow block building**: + +- Query: + +```honeycomb +VISUALIZE +AVG(duration_ms) +WHERE +name = "block_builder.build_block" +``` + +- Trigger condition: `AVG(duration_ms) > 30000` (adjust based on your expected block time) +- Description: Alert when blocks take too long to build (more than 30 seconds on average) + +**High failure rate**: + +- Query: + +```honeycomb +VISUALIZE +COUNT +WHERE +name = "block_builder.build_block" AND error = true +``` + +- Trigger condition: `COUNT > 100 WHERE error = true` +- Description: Alert when more than 100 block builds are failing + +#### Advanced investigation with BubbleUp + +To identify the root cause of performance issues or errors, use Honeycomb's BubbleUp feature: + +1. Create a query for a specific issue (e.g., high latency for block building) +2. Click on a specific high-latency point in the visualization +3. Use BubbleUp to see which attributes differ significantly between normal and slow operations +4. Inspect the related spans in the trace to pinpoint the exact step causing problems + +This approach helps identify patterns like: + +- Which types of transactions are causing slow blocks +- Which specific operations within block/batch processing take the most time +- Correlations between resource usage and performance +- Common patterns in error cases diff --git a/versioned_docs/version-0.12 (stable)/miden-node/operator/usage.md b/versioned_docs/version-0.12 (stable)/miden-node/operator/usage.md new file mode 100644 index 0000000..fa48617 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-node/operator/usage.md @@ -0,0 +1,132 @@ +--- +sidebar_position: 4 +--- + +# Configuration and Usage + +As outlined in the [Architecture](./architecture) chapter, the node consists of several components which can be run +separately or as a single bundled process. At present, the recommended way to operate a node is in bundled mode and is +what this guide will focus on. Operating the components separately is very similar and should be relatively +straight-forward to derive from these instructions. + +This guide focuses on basic usage. To discover more advanced options we recommend exploring the various help menus +which can be accessed by appending `--help` to any of the commands. + +## Bootstrapping + +The first step in starting a new Miden network is to initialize the genesis block data. This is a +one-off operation using the `bootstrap` command and by default the genesis block will contain a single +faucet account. + +```sh +# Create a folder to store the node's data. +mkdir data + +# Bootstrap the node. +# +# This creates the node's database and initializes it with the genesis data. +# +# The genesis block currently contains a single public faucet account. The +# secret for this account is stored in the `` +# file. This file is not used by the node and should instead by used wherever +# you intend to operate this faucet account. +# +# For example, you could operate a public faucet using our faucet reference +# implementation whose operation is described in a later section. +miden-node bundled bootstrap \ + --data-directory data \ + --accounts-directory . +``` + +You can also configure the account and asset data in the genesis block by passing in a toml configuration file. +This is particularly useful for setting up test scenarios without requiring multiple rounds of +transactions to achieve the desired state. Any account secrets will be written to disk inside the +the provided `--accounts-directory` path in the process. + +```sh +miden-node bundled bootstrap \ + --data-directory data \ + --accounts-directory . \ + --genesis-config-file genesis.toml +``` + +The genesis configuration file should contain fee parameters, the native faucet, optionally other +fungible faucets, and also optionally, wallet definitions with assets, for example: + +```toml +# The UNIX timestamp of the genesis block. It will influence the hash of the genesis block. +timestamp = 1717344256 +# Defines the format of the block protocol to use for the genesis block. +version = 1 + +# The native faucet to use for fees. +[native_faucet] +symbol = "MIDEN" +decimals = 6 +max_supply = 100_000_000_000_000_000 + +# The fee parameters to use for the genesis block. +[fee_parameters] +verification_base_fee = 0 + +# Another fungible faucet (optional) to initialize at genesis. +[[fungible_faucet]] +# The token symbol to use for the token +symbol = "FUZZY" +# Number of decimals your token will have, it effectively defines the fixed point accuracy. +decimals = 6 +# Total supply, in _base units_ +# +# e.g. a max supply of `1e15` _base units_ and decimals set to `6`, will yield you a total supply +# of `1e15/1e6 = 1e9` `FUZZY`s. +max_supply = 1_000_000_000_000_000 +# Storage mode of the faucet account. +storage_mode = "public" + + +[[wallet]] +# List of all assets the account should hold. Each token type _must_ have a corresponding faucet. +# The number is in _base units_, e.g. specifying `999 FUZZY` at 6 decimals would become +# `999_000_000`. +assets = [{ amount = 999_000_000, symbol = "FUZZY" }] +# Storage mode of the wallet account. +storage_mode = "private" +# The code of the account can be updated or not. +# has_updatable_code = false # default value +``` + +## Operation + +Start the node with the desired public gRPC server address. + +```sh +miden-node bundled start \ + --data-directory data \ + --rpc.url http://0.0.0.0:57291 +``` + +## Systemd + +Our [Debian packages](./installation.md#debian-package) install a systemd service which operates the node in `bundled` +mode. You'll still need to run the [bootstrapping](#bootstrapping) process before the node can be started. + +You can inspect the service file with `systemctl cat miden-node` or alternatively you can see it in +our repository in the `packaging` folder. For the bootstrapping process be sure to specify the data-directory as +expected by the systemd file. + +## Environment variables + +Most configuration options can also be configured using environment variables as an alternative to providing the values +via the command-line. This is useful for certain deployment options like `docker` or `systemd`, where they can be easier +to define or inject instead of changing the underlying command line options. + +These are especially convenient where multiple different configuration profiles are used. Write the environment +variables to some specific `profile.env` file and load it as part of the node command: + +```sh +source profile.env && miden-node <...> +``` + +This works well on Linux and MacOS, but Windows requires some additional scripting unfortunately. + +See the `.env` files in each of the binary crates' [directories](https://github.com/0xMiden/miden-node/tree/next/bin) for a list of all available environment variables. diff --git a/versioned_docs/version-0.12 (stable)/miden-node/operator/versioning.md b/versioned_docs/version-0.12 (stable)/miden-node/operator/versioning.md new file mode 100644 index 0000000..d678d69 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-node/operator/versioning.md @@ -0,0 +1,18 @@ +--- +sidebar_position: 6 +--- + +# Versioning + +We follow the [semver](https://semver.org/) standard for versioning. + +The following is considered the node's public API, and will therefore be considered as breaking changes. + +- RPC gRPC specification (note that this _excludes_ internal inter-component gRPC schemas). +- Node configuration options. +- Database schema changes which cannot be reverted. +- Large protocol and behavioral changes. + +We intend to include our OpenTelemetry trace specification in this once it stabilizes. + +We _will_ also call out non-breaking behavioral changes in our changelog and release notes. diff --git a/versioned_docs/version-0.12 (stable)/miden-node/rpc.md b/versioned_docs/version-0.12 (stable)/miden-node/rpc.md new file mode 100644 index 0000000..606f5cf --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-node/rpc.md @@ -0,0 +1,161 @@ +--- +title: "Node gRPC Reference" +sidebar_position: 1 +--- + +# gRPC Reference + +This is a reference of the Node's public RPC interface. It consists of a gRPC API which may be used to submit transactions and query the state of the blockchain. + +The gRPC service definition can be found in the Miden node's `proto` [directory](https://github.com/0xMiden/miden-node/tree/main/proto) in the `rpc.proto` file. + + + +- [CheckNullifiers](#checknullifiers) +- [GetAccountDetails](#getaccountdetails) +- [GetAccountProofs](#getaccountproofs) +- [GetBlockByNumber](#getblockbynumber) +- [GetBlockHeaderByNumber](#getblockheaderbynumber) +- [GetNotesById](#getnotesbyid) +- [GetNoteScriptByRoot](#getnotescriptbyroot) +- [SubmitProvenTransaction](#submitproventransaction) +- [SyncNullifiers](#syncnullifiers) +- [SyncAccountVault](#syncaccountvault) +- [SyncNotes](#syncnotes) +- [SyncState](#syncstate) +- [SyncStorageMaps](#syncstoragemaps) +- [SyncTransactions](#synctransactions) +- [Status](#status) + + + +## API Endpoints + +### CheckNullifiers + +Request proofs for a set of nullifiers. + +### GetAccountDetails + +Request the latest state of an account. + +### GetAccountProofs + +Request state proofs for accounts, including specific storage slots. + +### GetBlockByNumber + +Request the raw data for a specific block. + +### GetBlockHeaderByNumber + +Request a specific block header and its inclusion proof. + +### GetNotesById + +Request a set of notes. + +### GetNoteScriptByRoot + +Request the script for a note by its root. + +### SubmitProvenTransaction + +Submit a transaction to the network. + +This endpoint accepts a proven transaction and attempts to add it to the mempool for inclusion in future blocks. The transaction must be properly formatted and include a valid execution proof. + +#### Error Codes + +When transaction submission fails, detailed error information is provided through gRPC status details. The following error codes may be returned: + +| Error Code | Value | gRPC Status | Description | +|-----------------------------------------------|-------|--------------------|---------------------------------------------------------------| +| `INTERNAL_ERROR` | 0 | `INTERNAL` | Internal server error occurred | +| `DESERIALIZATION_FAILED` | 1 | `INVALID_ARGUMENT` | Transaction could not be deserialized | +| `INVALID_TRANSACTION_PROOF` | 2 | `INVALID_ARGUMENT` | Transaction execution proof is invalid | +| `INCORRECT_ACCOUNT_INITIAL_COMMITMENT` | 3 | `INVALID_ARGUMENT` | Account's initial state doesn't match current state | +| `INPUT_NOTES_ALREADY_CONSUMED` | 4 | `INVALID_ARGUMENT` | Input notes have already been consumed by another transaction | +| `UNAUTHENTICATED_NOTES_NOT_FOUND` | 5 | `INVALID_ARGUMENT` | Required unauthenticated notes were not found | +| `OUTPUT_NOTES_ALREADY_EXIST` | 6 | `INVALID_ARGUMENT` | Output note IDs are already in use | +| `TRANSACTION_EXPIRED` | 7 | `INVALID_ARGUMENT` | Transaction has exceeded its expiration block height | + +### SyncNullifiers + +Returns nullifier synchronization data for a set of prefixes within a given block range. This method allows clients to efficiently track nullifier creation by retrieving only the nullifiers produced between two blocks. + +Caller specifies the `prefix_len` (currently only 16), the list of prefix values (`nullifiers`), and the block range (`block_from`, optional `block_to`). The response includes all matching nullifiers created within that range, the last block included in the response (`block_num`), and the current chain tip (`chain_tip`). + +If the response is chunked (i.e., `block_num < block_to`), continue by issuing another request with `block_from = block_num + 1` to retrieve subsequent updates. + +### SyncAccountVault + +Returns information that allows clients to sync asset values for specific public accounts within a block range. + +For any `[block_from..block_to]` range, the latest known set of assets is returned for the requested account ID. The data can be split and a cutoff block may be selected if there are too many assets to sync. The response contains the chain tip so that the caller knows when it has been reached. + +### SyncNotes + +Iteratively sync data for a given set of note tags. + +Client specifies the `note_tags` they are interested in, and the block range from which to search for matching notes. The request will then return the next block containing any note matching the provided tags within the specified range. + +The response includes each note's metadata and inclusion proof. + +A basic note sync can be implemented by repeatedly requesting the previous response's block until reaching the tip of the chain. + +### SyncState + +Iteratively sync data for specific notes and accounts. + +This request returns the next block containing data of interest. Client is expected to repeat these requests in a loop until the response reaches the head of the chain, at which point the data is fully synced. + +Each update response also contains info about new notes, accounts etc. created. It also returns Chain MMR delta that can be used to update the state of Chain MMR. This includes both chain MMR peaks and chain MMR nodes. + +The low part of note tags are redacted to preserve some degree of privacy. Returned data therefore contains additional notes which should be filtered out by the client. + +### SyncStorageMaps + +Returns storage map synchronization data for a specified public account within a given block range. This method allows clients to efficiently sync the storage map state of an account by retrieving only the changes that occurred between two blocks. + +Caller specifies the `account_id` of the public account and the block range (`block_from`, `block_to`) for which to retrieve storage updates. The response includes all storage map key-value updates that occurred within that range, along with the last block included in the sync and the current chain tip. + +This endpoint enables clients to maintain an updated view of account storage. + +### SyncTransactions + +Returns transaction records for specific accounts within a block range. + +### Status + +Request the status of the node components. The response contains the current version of the RPC component and the connection status of the other components, including their versions and the number of the most recent block in the chain (chain tip). + +## Error Handling + +The Miden node uses standard gRPC error reporting mechanisms. When an RPC call fails, a `Status` object is returned containing: + +- **Status Code**: Standard gRPC status codes (`INVALID_ARGUMENT`, `INTERNAL`, etc.). +- **Message**: Human-readable error description. +- **Details**: Additional structured error information (when available). + +For critical operations like transaction submission, detailed error codes are provided in the `Status.details` field to help clients understand the specific failure reason and take appropriate action. + +### Error Details Format + +The `Status.details` field contains the specific error code serialized as raw bytes: + +- **Format**: Single byte containing the numeric error code value +- **Decoding**: Read the first byte to get the error code +- **Mapping**: Map the numeric value to the corresponding error enum + +**Example decoding** (pseudocode): + +``` +if status.details.length > 0: + error_code = status.details[0] // Extract first byte + switch error_code: + case 1: return "INTERNAL_ERROR" + case 2: return "DESERIALIZATION_FAILED" + case 5: return "INPUT_NOTES_ALREADY_CONSUMED" + // ... etc +``` diff --git a/versioned_docs/version-0.12 (stable)/miden-node/theme/Admonition/Icon/Danger.tsx b/versioned_docs/version-0.12 (stable)/miden-node/theme/Admonition/Icon/Danger.tsx new file mode 100644 index 0000000..ab14d7d --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-node/theme/Admonition/Icon/Danger.tsx @@ -0,0 +1,19 @@ +import React, { type ReactNode } from "react"; +import type { Props } from "@theme/Admonition/Icon/Danger"; + +export default function AdmonitionIconDanger(props: Props): ReactNode { + return ( + + + + ); +} diff --git a/versioned_docs/version-0.12 (stable)/miden-node/theme/Admonition/Icon/Info.tsx b/versioned_docs/version-0.12 (stable)/miden-node/theme/Admonition/Icon/Info.tsx new file mode 100644 index 0000000..59e48a5 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-node/theme/Admonition/Icon/Info.tsx @@ -0,0 +1,20 @@ +import React, { type ReactNode } from "react"; +import type { Props } from "@theme/Admonition/Icon/Info"; + +export default function AdmonitionIconInfo(props: Props): ReactNode { + return ( + + + + ); +} diff --git a/versioned_docs/version-0.12 (stable)/miden-node/theme/Admonition/Icon/Note.tsx b/versioned_docs/version-0.12 (stable)/miden-node/theme/Admonition/Icon/Note.tsx new file mode 100644 index 0000000..d7c524b --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-node/theme/Admonition/Icon/Note.tsx @@ -0,0 +1,20 @@ +import React, { type ReactNode } from "react"; +import type { Props } from "@theme/Admonition/Icon/Note"; + +export default function AdmonitionIconNote(props: Props): ReactNode { + return ( + + + + ); +} diff --git a/versioned_docs/version-0.12 (stable)/miden-node/theme/Admonition/Icon/Tip.tsx b/versioned_docs/version-0.12 (stable)/miden-node/theme/Admonition/Icon/Tip.tsx new file mode 100644 index 0000000..219bb8d --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-node/theme/Admonition/Icon/Tip.tsx @@ -0,0 +1,20 @@ +import React, { type ReactNode } from "react"; +import type { Props } from "@theme/Admonition/Icon/Tip"; + +export default function AdmonitionIconTip(props: Props): ReactNode { + return ( + + + + ); +} diff --git a/versioned_docs/version-0.12 (stable)/miden-node/theme/Admonition/Icon/Warning.tsx b/versioned_docs/version-0.12 (stable)/miden-node/theme/Admonition/Icon/Warning.tsx new file mode 100644 index 0000000..f96398d --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-node/theme/Admonition/Icon/Warning.tsx @@ -0,0 +1,20 @@ +import React, { type ReactNode } from "react"; +import type { Props } from "@theme/Admonition/Icon/Warning"; + +export default function AdmonitionIconCaution(props: Props): ReactNode { + return ( + + + + ); +} diff --git a/versioned_docs/version-0.12 (stable)/miden-node/theme/Admonition/Layout/index.tsx b/versioned_docs/version-0.12 (stable)/miden-node/theme/Admonition/Layout/index.tsx new file mode 100644 index 0000000..7b2c170 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-node/theme/Admonition/Layout/index.tsx @@ -0,0 +1,51 @@ +import React, { type ReactNode } from "react"; +import clsx from "clsx"; +import { ThemeClassNames } from "@docusaurus/theme-common"; + +import type { Props } from "@theme/Admonition/Layout"; + +import styles from "./styles.module.css"; + +function AdmonitionContainer({ + type, + className, + children, +}: Pick & { children: ReactNode }) { + return ( +
+ {children} +
+ ); +} + +function AdmonitionHeading({ icon, title }: Pick) { + return ( +
+ {icon} + {/* {title} */} +
+ ); +} + +function AdmonitionContent({ children }: Pick) { + return children ? ( +
{children}
+ ) : null; +} + +export default function AdmonitionLayout(props: Props): ReactNode { + const { type, icon, title, children, className } = props; + return ( + + {title || icon ? : null} + {children} + + ); +} diff --git a/versioned_docs/version-0.12 (stable)/miden-node/theme/Admonition/Layout/styles.module.css b/versioned_docs/version-0.12 (stable)/miden-node/theme/Admonition/Layout/styles.module.css new file mode 100644 index 0000000..88df7e6 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-node/theme/Admonition/Layout/styles.module.css @@ -0,0 +1,35 @@ +.admonition { + margin-bottom: 1em; +} + +.admonitionHeading { + font: var(--ifm-heading-font-weight) var(--ifm-h5-font-size) / + var(--ifm-heading-line-height) var(--ifm-heading-font-family); + text-transform: uppercase; +} + +/* Heading alone without content (does not handle fragment content) */ +.admonitionHeading:not(:last-child) { + margin-bottom: 0.3rem; +} + +.admonitionHeading code { + text-transform: none; +} + +.admonitionIcon { + display: inline-block; + vertical-align: middle; + margin-right: 0.4em; +} + +.admonitionIcon svg { + display: inline-block; + height: 1.6em; + width: 1.6em; + fill: var(--ifm-alert-foreground-color); +} + +.admonitionContent > :last-child { + margin-bottom: 0; +} diff --git a/versioned_docs/version-0.12 (stable)/miden-node/theme/Admonition/Type/Caution.tsx b/versioned_docs/version-0.12 (stable)/miden-node/theme/Admonition/Type/Caution.tsx new file mode 100644 index 0000000..b570a37 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-node/theme/Admonition/Type/Caution.tsx @@ -0,0 +1,32 @@ +import React, {type ReactNode} from 'react'; +import clsx from 'clsx'; +import Translate from '@docusaurus/Translate'; +import type {Props} from '@theme/Admonition/Type/Caution'; +import AdmonitionLayout from '@theme/Admonition/Layout'; +import IconWarning from '@theme/Admonition/Icon/Warning'; + +const infimaClassName = 'alert alert--warning'; + +const defaultProps = { + icon: , + title: ( + + caution + + ), +}; + +// TODO remove before v4: Caution replaced by Warning +// see https://github.com/facebook/docusaurus/issues/7558 +export default function AdmonitionTypeCaution(props: Props): ReactNode { + return ( + + {props.children} + + ); +} diff --git a/versioned_docs/version-0.12 (stable)/miden-node/theme/Admonition/Type/Danger.tsx b/versioned_docs/version-0.12 (stable)/miden-node/theme/Admonition/Type/Danger.tsx new file mode 100644 index 0000000..49901fa --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-node/theme/Admonition/Type/Danger.tsx @@ -0,0 +1,30 @@ +import React, {type ReactNode} from 'react'; +import clsx from 'clsx'; +import Translate from '@docusaurus/Translate'; +import type {Props} from '@theme/Admonition/Type/Danger'; +import AdmonitionLayout from '@theme/Admonition/Layout'; +import IconDanger from '@theme/Admonition/Icon/Danger'; + +const infimaClassName = 'alert alert--danger'; + +const defaultProps = { + icon: , + title: ( + + danger + + ), +}; + +export default function AdmonitionTypeDanger(props: Props): ReactNode { + return ( + + {props.children} + + ); +} diff --git a/versioned_docs/version-0.12 (stable)/miden-node/theme/Admonition/Type/Info.tsx b/versioned_docs/version-0.12 (stable)/miden-node/theme/Admonition/Type/Info.tsx new file mode 100644 index 0000000..018e0a1 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-node/theme/Admonition/Type/Info.tsx @@ -0,0 +1,30 @@ +import React, {type ReactNode} from 'react'; +import clsx from 'clsx'; +import Translate from '@docusaurus/Translate'; +import type {Props} from '@theme/Admonition/Type/Info'; +import AdmonitionLayout from '@theme/Admonition/Layout'; +import IconInfo from '@theme/Admonition/Icon/Info'; + +const infimaClassName = 'alert alert--info'; + +const defaultProps = { + icon: , + title: ( + + info + + ), +}; + +export default function AdmonitionTypeInfo(props: Props): ReactNode { + return ( + + {props.children} + + ); +} diff --git a/versioned_docs/version-0.12 (stable)/miden-node/theme/Admonition/Type/Note.tsx b/versioned_docs/version-0.12 (stable)/miden-node/theme/Admonition/Type/Note.tsx new file mode 100644 index 0000000..c99e038 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-node/theme/Admonition/Type/Note.tsx @@ -0,0 +1,30 @@ +import React, {type ReactNode} from 'react'; +import clsx from 'clsx'; +import Translate from '@docusaurus/Translate'; +import type {Props} from '@theme/Admonition/Type/Note'; +import AdmonitionLayout from '@theme/Admonition/Layout'; +import IconNote from '@theme/Admonition/Icon/Note'; + +const infimaClassName = 'alert alert--secondary'; + +const defaultProps = { + icon: , + title: ( + + note + + ), +}; + +export default function AdmonitionTypeNote(props: Props): ReactNode { + return ( + + {props.children} + + ); +} diff --git a/versioned_docs/version-0.12 (stable)/miden-node/theme/Admonition/Type/Tip.tsx b/versioned_docs/version-0.12 (stable)/miden-node/theme/Admonition/Type/Tip.tsx new file mode 100644 index 0000000..18604a5 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-node/theme/Admonition/Type/Tip.tsx @@ -0,0 +1,30 @@ +import React, {type ReactNode} from 'react'; +import clsx from 'clsx'; +import Translate from '@docusaurus/Translate'; +import type {Props} from '@theme/Admonition/Type/Tip'; +import AdmonitionLayout from '@theme/Admonition/Layout'; +import IconTip from '@theme/Admonition/Icon/Tip'; + +const infimaClassName = 'alert alert--success'; + +const defaultProps = { + icon: , + title: ( + + tip + + ), +}; + +export default function AdmonitionTypeTip(props: Props): ReactNode { + return ( + + {props.children} + + ); +} diff --git a/versioned_docs/version-0.12 (stable)/miden-node/theme/Admonition/Type/Warning.tsx b/versioned_docs/version-0.12 (stable)/miden-node/theme/Admonition/Type/Warning.tsx new file mode 100644 index 0000000..61d9597 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-node/theme/Admonition/Type/Warning.tsx @@ -0,0 +1,30 @@ +import React, {type ReactNode} from 'react'; +import clsx from 'clsx'; +import Translate from '@docusaurus/Translate'; +import type {Props} from '@theme/Admonition/Type/Warning'; +import AdmonitionLayout from '@theme/Admonition/Layout'; +import IconWarning from '@theme/Admonition/Icon/Warning'; + +const infimaClassName = 'alert alert--warning'; + +const defaultProps = { + icon: , + title: ( + + warning + + ), +}; + +export default function AdmonitionTypeWarning(props: Props): ReactNode { + return ( + + {props.children} + + ); +} diff --git a/versioned_docs/version-0.12 (stable)/miden-node/theme/Admonition/Types.tsx b/versioned_docs/version-0.12 (stable)/miden-node/theme/Admonition/Types.tsx new file mode 100644 index 0000000..2a10019 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-node/theme/Admonition/Types.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import AdmonitionTypeNote from '@theme/Admonition/Type/Note'; +import AdmonitionTypeTip from '@theme/Admonition/Type/Tip'; +import AdmonitionTypeInfo from '@theme/Admonition/Type/Info'; +import AdmonitionTypeWarning from '@theme/Admonition/Type/Warning'; +import AdmonitionTypeDanger from '@theme/Admonition/Type/Danger'; +import AdmonitionTypeCaution from '@theme/Admonition/Type/Caution'; +import type AdmonitionTypes from '@theme/Admonition/Types'; + +const admonitionTypes: typeof AdmonitionTypes = { + note: AdmonitionTypeNote, + tip: AdmonitionTypeTip, + info: AdmonitionTypeInfo, + warning: AdmonitionTypeWarning, + danger: AdmonitionTypeDanger, +}; + +// Undocumented legacy admonition type aliases +// Provide hardcoded/untranslated retrocompatible label +// See also https://github.com/facebook/docusaurus/issues/7767 +const admonitionAliases: typeof AdmonitionTypes = { + secondary: (props) => , + important: (props) => , + success: (props) => , + caution: AdmonitionTypeCaution, +}; + +export default { + ...admonitionTypes, + ...admonitionAliases, +}; diff --git a/versioned_docs/version-0.12 (stable)/miden-node/theme/Admonition/index.tsx b/versioned_docs/version-0.12 (stable)/miden-node/theme/Admonition/index.tsx new file mode 100644 index 0000000..8f4225d --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-node/theme/Admonition/index.tsx @@ -0,0 +1,21 @@ +import React, {type ComponentType, type ReactNode} from 'react'; +import {processAdmonitionProps} from '@docusaurus/theme-common'; +import type {Props} from '@theme/Admonition'; +import AdmonitionTypes from '@theme/Admonition/Types'; + +function getAdmonitionTypeComponent(type: string): ComponentType { + const component = AdmonitionTypes[type]; + if (component) { + return component; + } + console.warn( + `No admonition component found for admonition type "${type}". Using Info as fallback.`, + ); + return AdmonitionTypes.info!; +} + +export default function Admonition(unprocessedProps: Props): ReactNode { + const props = processAdmonitionProps(unprocessedProps); + const AdmonitionTypeComponent = getAdmonitionTypeComponent(props.type); + return ; +} diff --git a/versioned_docs/version-0.12 (stable)/miden-tutorials/_category_.yml b/versioned_docs/version-0.12 (stable)/miden-tutorials/_category_.yml new file mode 100644 index 0000000..e2ceea5 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-tutorials/_category_.yml @@ -0,0 +1,4 @@ +label: "Tutorials" +# Determines where this documentation section appears relative to other sections on the main documentation page (which is the parent of this folder in the miden-docs repository) +position: 4 +collapsed: true diff --git a/versioned_docs/version-0.12 (stable)/miden-tutorials/img/count_copy_fpi_diagram.png b/versioned_docs/version-0.12 (stable)/miden-tutorials/img/count_copy_fpi_diagram.png new file mode 100644 index 0000000..f0fd302 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-tutorials/img/count_copy_fpi_diagram.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-tutorials/img/note_creation_masm.png b/versioned_docs/version-0.12 (stable)/miden-tutorials/img/note_creation_masm.png new file mode 100644 index 0000000..9458433 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-tutorials/img/note_creation_masm.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-tutorials/index.md b/versioned_docs/version-0.12 (stable)/miden-tutorials/index.md new file mode 100644 index 0000000..d112f22 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-tutorials/index.md @@ -0,0 +1,12 @@ +--- +title: Tutorials +sidebar_position: 4 +--- + +# Introduction + +Basic tutorials and examples of how to build applications on Miden. + +The goal is to make getting up to speed with building on Miden as quick and simple as possible. + +All of the following tutorials are accompanied by code examples in Rust and TypeScript, which can be found in the [Miden Tutorials](https://github.com/0xMiden/miden-tutorials) repository. diff --git a/versioned_docs/version-0.12 (stable)/miden-tutorials/lib.rs b/versioned_docs/version-0.12 (stable)/miden-tutorials/lib.rs new file mode 100644 index 0000000..2cf4c50 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-tutorials/lib.rs @@ -0,0 +1,11 @@ +#![doc = include_str!("rust-client/create_deploy_tutorial.md")] +#![doc = include_str!("rust-client/mint_consume_create_tutorial.md")] +#![doc = include_str!("rust-client/counter_contract_tutorial.md")] +#![doc = include_str!("rust-client/custom_note_how_to.md")] +#![doc = include_str!("rust-client/foreign_procedure_invocation_tutorial.md")] +#![doc = include_str!("rust-client/public_account_interaction_tutorial.md")] +#![doc = include_str!("rust-client/unauthenticated_note_how_to.md")] +#![doc = include_str!("rust-client/mappings_in_masm_how_to.md")] +#![doc = include_str!("rust-client/creating_notes_in_masm_tutorial.md")] +#![doc = include_str!("rust-client/delegated_proving_tutorial.md")] +#![doc = include_str!("rust-client/network_transactions_tutorial.md")] diff --git a/versioned_docs/version-0.12 (stable)/miden-tutorials/miden_node_setup.md b/versioned_docs/version-0.12 (stable)/miden-tutorials/miden_node_setup.md new file mode 100644 index 0000000..ebeefb7 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-tutorials/miden_node_setup.md @@ -0,0 +1,87 @@ +--- +title: Miden Node Setup +sidebar_position: 2 +--- + +# Miden Node Setup Tutorial + +To run the Miden tutorial examples, you will need to set up a test environment and connect to a Miden node. + +There are two ways to connect to a Miden node: + +1. Run the Miden node locally +2. Connect to the Miden testnet + +## Running the Miden node locally + +### Step 1: Install the Miden node + +Next, install the miden-node crate using this command: + +```bash +cargo install miden-node --locked --version 0.12.3 +``` + +### Step 2: Initializing the node + +To start the node, we first need to generate the genesis file. Create the genesis file using this command: + +```bash +mkdir data +mkdir accounts + +miden-node bundled bootstrap \ + --data-directory data \ + --accounts-directory accounts +``` + +Expected output: + +``` +2025-04-16T18:05:30.049129Z INFO miden_node::commands::store: bin/node/src/commands/store.rs:145: Generating account, index: 0, total: 1 +``` + +### Step 3: Starting the node + +To start the node run this command: + +```bash +miden-node bundled start \ + --data-directory data \ + --rpc.url http://0.0.0.0:57291 +``` + +Expected output: + +``` +2025-01-17T12:14:55.432445Z INFO try_build_batches: miden-block-producer: /Users/username/.cargo/registry/src/index.crates.io-6f17d22bba15001f/miden-node-block-producer-0.6.0/src/txqueue/mod.rs:85: close, time.busy: 8.88µs, time.idle: 103µs +2025-01-17T12:14:57.433162Z INFO try_build_batches: miden-block-producer: /Users/username/.cargo/registry/src/index.crates.io-6f17d22bba15001f/miden-node-block-producer-0.6.0/src/txqueue/mod.rs:85: new +2025-01-17T12:14:57.433256Z INFO try_build_batches: miden-block-producer: /Users/username/.cargo/registry/src/index.crates.io-6f17d22bba15001f/miden-node-block-producer-0.6.0/src/txqueue/mod.rs:85: close, time.busy: 6.46µs, time.idle: 94.0µs +``` + +Congratulations, you now have a Miden node running locally. Now we can start creating a testing environment for building applications on Miden! + +The endpoint of the Miden node running locally is: + +``` +http://localhost:57291 +``` + +### Resetting the node + +_If you need to reset the local state of the node run this command:_ + +```bash +rm -r data +rm -r accounts +``` + +After resetting the state of the node, follow steps 2 and 4 again. + +## Connecting to the Miden testnet + +To run the tutorial examples using the Miden testnet, use this endpoint: + +```bash +https://rpc.testnet.miden.io:443 +``` diff --git a/versioned_docs/version-0.12 (stable)/miden-tutorials/rust-client/_category_.yml b/versioned_docs/version-0.12 (stable)/miden-tutorials/rust-client/_category_.yml new file mode 100644 index 0000000..5fcef20 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-tutorials/rust-client/_category_.yml @@ -0,0 +1,4 @@ +label: "Rust Client" +# Determines where this documentation section appears relative to other sections in the parent folder +position: 2 +collapsed: true diff --git a/versioned_docs/version-0.12 (stable)/miden-tutorials/rust-client/counter_contract_tutorial.md b/versioned_docs/version-0.12 (stable)/miden-tutorials/rust-client/counter_contract_tutorial.md new file mode 100644 index 0000000..56e4a16 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-tutorials/rust-client/counter_contract_tutorial.md @@ -0,0 +1,594 @@ +--- +title: "Deploying a Counter Contract" +sidebar_position: 4 +--- + +# Deploying a Counter Contract + +_Using the Miden client in Rust to deploy and interact with a custom smart contract on Miden_ + +## Overview + +In this tutorial, we will build a simple counter smart contract that maintains a count, deploy it to the Miden testnet, and interact with it by incrementing the count. You can also deploy the counter contract on a locally running Miden node, similar to previous tutorials. + +Using a script, we will invoke the increment function within the counter contract to update the count. This tutorial provides a foundational understanding of developing and deploying custom smart contracts on Miden. + +## What we'll cover + +- Deploying a custom smart contract on Miden +- Getting up to speed with the basics of Miden assembly +- Calling procedures in an account +- Pure vs state changing procedures + +## Prerequisites + +This tutorial assumes you have a basic understanding of Miden assembly. To quickly get up to speed with Miden assembly (MASM), please play around with running basic Miden assembly programs in the [Miden playground](https://0xMiden.github.io/examples/). + +## Step 1: Initialize your repository + +Create a new Rust repository for your Miden project and navigate to it with the following command: + +```bash +cargo new miden-counter-contract +cd miden-counter-contract +``` + +Add the following dependencies to your `Cargo.toml` file: + +```toml +[dependencies] +miden-client = { version = "0.12", features = ["testing", "tonic"] } +miden-client-sqlite-store = { version = "0.12", package = "miden-client-sqlite-store" } +miden-lib = { version = "0.12", default-features = false } +miden-objects = { version = "0.12", default-features = false, features = ["testing"] } +miden-crypto = { version = "0.17.1", features = ["executable"] } +miden-assembly = "0.18.3" +rand = { version = "0.9" } +serde = { version = "1", features = ["derive"] } +serde_json = { version = "1.0", features = ["raw_value"] } +tokio = { version = "1.46", features = ["rt-multi-thread", "net", "macros", "fs"] } +rand_chacha = "0.9.0" +``` + +### Set up your `src/main.rs` file + +In the previous section, we explained how to instantiate the Miden client. We can reuse the same `initialize_client` function for our counter contract. + +Copy and paste the following code into your `src/main.rs` file: + +```rust no_run +use miden_lib::account::auth::NoAuth; +use miden_lib::transaction::TransactionKernel; +use rand::{rngs::StdRng, RngCore}; +use std::{fs, path::Path, sync::Arc}; + +use miden_client::{ + address::NetworkId, + assembly::{Assembler, DefaultSourceManager, LibraryPath, Module, ModuleKind}, + builder::ClientBuilder, + keystore::FilesystemKeyStore, + rpc::{Endpoint, GrpcClient}, + transaction::TransactionRequestBuilder, + ClientError, +}; +use miden_client_sqlite_store::ClientBuilderSqliteExt; +use miden_objects::{ + account::{AccountBuilder, AccountComponent, AccountStorageMode, AccountType, StorageSlot}, + Word, +}; + +fn create_library( + assembler: Assembler, + library_path: &str, + source_code: &str, +) -> Result> { + let source_manager = Arc::new(DefaultSourceManager::default()); + let module = Module::parser(ModuleKind::Library).parse_str( + LibraryPath::new(library_path)?, + source_code, + &source_manager, + )?; + let library = assembler.clone().assemble_library([module])?; + Ok(library) +} + +#[tokio::main] +async fn main() -> Result<(), ClientError> { + // Initialize client + let endpoint = Endpoint::testnet(); + let timeout_ms = 10_000; + let rpc_client = Arc::new(GrpcClient::new(&endpoint, timeout_ms)); + + // Initialize keystore + let keystore_path = std::path::PathBuf::from("./keystore"); + let keystore = Arc::new(FilesystemKeyStore::::new(keystore_path).unwrap()); + + let store_path = std::path::PathBuf::from("./store.sqlite3"); + + let mut client = ClientBuilder::new() + .rpc(rpc_client) + .sqlite_store(store_path) + .authenticator(keystore.clone()) + .in_debug_mode(true.into()) + .build() + .await?; + + let sync_summary = client.sync_state().await.unwrap(); + println!("Latest block: {}", sync_summary.block_num); + + Ok(()) +} +``` + +_When running the code above, there will be some unused imports, however, we will use these imports later on in the tutorial._ + +**Note**: Running the code above, will generate a `store.sqlite3` file and a `keystore` directory. The Miden client uses the `store.sqlite3` file to keep track of the state of accounts and notes. The `keystore` directory keeps track of private keys used by accounts. Be sure to add both to your `.gitignore`! + +## Step 2: Build the counter contract + +For better code organization, we will separate the Miden assembly code from our Rust code. + +Create a directory named `masm` at the **root** of your `miden-counter-contract` directory. This will contain our contract and script masm code. + +Initialize the `masm` directory: + +```bash +mkdir -p masm/accounts masm/scripts +``` + +This will create: + +```text +masm/ +├── accounts/ +└── scripts/ +``` + +### Custom Miden smart contract + +Below is our counter contract. It has a two exported procedures: `get_count` and `increment_count`. + +At the beginning of the MASM file, we define our imports. In this case, we import `miden::account` and `std::sys`. + +The import `miden::account` contains useful procedures for interacting with a smart contract's state. + +The import `std::sys` contains a useful procedure for truncating the operand stack at the end of a procedure. + +#### Here's a breakdown of what the `get_count` procedure does: + +1. Pushes `0` (COUNTER_SLOT) onto the stack, representing the index of the storage slot to read. +2. Calls `account::get_item` with the index of `0`. +3. Calls `sys::truncate_stack` to truncate the stack to size 16. +4. The value returned from `account::get_item` is still on the stack and will be returned when this procedure is called. + +#### Here's a breakdown of what the `increment_count` procedure does: + +1. Pushes `0` (COUNTER_SLOT) onto the stack, representing the index of the storage slot to read. +2. Calls `active_account::get_item` with the index of `0`. +3. Pushes `1` onto the stack. +4. Adds `1` to the count value returned from `active_account::get_item`. +5. _For demonstration purposes_, calls `debug.stack` to see the state of the stack +6. Pushes `0` (COUNTER_SLOT) onto the stack, which is the index of the storage slot we want to write to. +7. Calls `native_account::set_item` which saves the incremented count to storage at index `0` +8. Uses `dropw` to clear the top 4 Stack elements, which is the old count value returned by the previous call + +Inside of the `masm/accounts/` directory, create the `counter.masm` file: + +```masm +use.miden::active_account +use.miden::native_account +use.std::sys + +const.COUNTER_SLOT=0 + +#! Inputs: [] +#! Outputs: [count] +export.get_count + push.COUNTER_SLOT + # => [index] + + exec.active_account::get_item + # => [count] + + # clean up stack + movdn.4 dropw + # => [count] +end + +#! Inputs: [] +#! Outputs: [] +export.increment_count + push.COUNTER_SLOT + # => [index] + + exec.active_account::get_item + # => [count] + + add.1 + # => [count+1] + + debug.stack + + push.COUNTER_SLOT + # [index, count+1] + + exec.native_account::set_item + # => [OLD_VALUE] + + dropw + # => [] +end + +``` + +**Note**: _It's a good habit to add comments below each line of MASM code with the expected stack state. This improves readability and helps with debugging._ + +### Authentication Component + +**Important**: Starting with Miden Client 0.10.0, all accounts must have an authentication component. For smart contracts that don't require authentication (like our counter contract), we use a `NoAuth` component. + +This `NoAuth` component allows any user to interact with the smart contract without requiring signature verification. + +### Custom script + +This is a Miden assembly script that will call the `increment_count` procedure during the transaction. + +The string `{increment_count}` will be replaced with the hash of the `increment_count` procedure in our rust program. + +Inside of the `masm/scripts/` directory, create the `counter_script.masm` file: + +```masm +use.external_contract::counter_contract + +begin + call.counter_contract::increment_count +end +``` + +## Step 3: Build the counter smart contract + +To build the counter contract copy and paste the following code at the end of your `src/main.rs` file: + +```rust ignore +// ------------------------------------------------------------------------- +// STEP 1: Create a basic counter contract +// ------------------------------------------------------------------------- +println!("\n[STEP 1] Creating counter contract."); + +// Load the MASM file for the counter contract +let counter_path = Path::new("../masm/accounts/counter.masm"); +let counter_code = fs::read_to_string(counter_path).unwrap(); + +// Compile the account code into `AccountComponent` with one storage slot +let counter_component = AccountComponent::compile( + &counter_code, + TransactionKernel::assembler(), + vec![StorageSlot::Value(Word::default())], +) +.unwrap() +.with_supports_all_types(); + +// Init seed for the counter contract +let mut seed = [0_u8; 32]; +client.rng().fill_bytes(&mut seed); + +// Build the new `Account` with the component +let counter_contract = AccountBuilder::new(seed) + .account_type(AccountType::RegularAccountImmutableCode) + .storage_mode(AccountStorageMode::Public) + .with_component(counter_component.clone()) + .with_auth_component(NoAuth) + .build() + .unwrap(); + +println!( + "counter_contract commitment: {:?}", + counter_contract.commitment() +); +println!("counter_contract id: {:?}", counter_contract.id()); +println!("counter_contract storage: {:?}", counter_contract.storage()); + +client.add_account(&counter_contract, false).await.unwrap(); +``` + +Run the following command to execute `src/main.rs`: + +```bash +cargo run --release +``` + +After the program executes, you should see the counter contract hash and contract id printed to the terminal, for example: + +```text +[STEP 1] Creating counter contract. +counter_contract commitment: RpoDigest([3700134472268167470, 14878091556015233722, 3335592073702485043, 16978997897830363420]) +counter_contract id: "mtst1qql030hpsp0yyqra494lcwazxsym7add" +counter_contract storage: AccountStorage { slots: [Value([0, 0, 0, 0]), Value([0, 0, 0, 0])] } +``` + +## Step 4: Incrementing the count + +Now that we built the counter contract, lets create a transaction request to increment the count: + +Paste the following code at the end of your `src/main.rs` file: + +```rust ignore +// ------------------------------------------------------------------------- +// STEP 2: Call the Counter Contract with a script +// ------------------------------------------------------------------------- +println!("\n[STEP 2] Call Counter Contract With Script"); + +// Load the MASM script referencing the increment procedure +let script_path = Path::new("../masm/scripts/counter_script.masm"); +let script_code = fs::read_to_string(script_path).unwrap(); + +// Create a library from the counter contract code +let assembler = TransactionKernel::assembler().with_debug_mode(true); +let account_component_lib = create_library( + assembler.clone(), + "external_contract::counter_contract", + &counter_code, +) +.unwrap(); + +let tx_script = client + .script_builder() + .with_dynamically_linked_library(&account_component_lib) + .unwrap() + .compile_tx_script(&script_code) + .unwrap(); + +// Build a transaction request with the custom script +let tx_increment_request = TransactionRequestBuilder::new() + .custom_script(tx_script) + .build() + .unwrap(); + +// Execute and submit the transaction +let tx_id = client + .submit_new_transaction(counter_contract.id(), tx_increment_request) + .await + .unwrap(); + +println!( + "View transaction on MidenScan: https://testnet.midenscan.com/tx/{:?}", + tx_id +); + +println!( + "Counter contract id: {:?}", + counter_contract.id().to_bech32(NetworkId::Testnet) +); + +client.sync_state().await.unwrap(); + +// Retrieve updated contract data to see the incremented counter +let account = client.get_account(counter_contract.id()).await.unwrap(); +println!( + "counter contract storage: {:?}", + account.unwrap().account().storage().get_item(0) +); +``` + +**Note**: _Once our counter contract is deployed, other users can increment the count of the smart contract simply by knowing the account id of the contract and the procedure hash of the `increment_count` procedure._ + +## Summary + +The final `src/main.rs` file should look like this: + +```rust +use miden_lib::account::auth::NoAuth; +use miden_lib::transaction::TransactionKernel; +use rand::{rngs::StdRng, RngCore}; +use std::{fs, path::Path, sync::Arc}; + +use miden_client::{ + address::NetworkId, + assembly::{Assembler, DefaultSourceManager, LibraryPath, Module, ModuleKind}, + builder::ClientBuilder, + keystore::FilesystemKeyStore, + rpc::{Endpoint, GrpcClient}, + transaction::TransactionRequestBuilder, + ClientError, +}; +use miden_client_sqlite_store::ClientBuilderSqliteExt; +use miden_objects::{ + account::{AccountBuilder, AccountComponent, AccountStorageMode, AccountType, StorageSlot}, + Word, +}; + +fn create_library( + assembler: Assembler, + library_path: &str, + source_code: &str, +) -> Result> { + let source_manager = Arc::new(DefaultSourceManager::default()); + let module = Module::parser(ModuleKind::Library).parse_str( + LibraryPath::new(library_path)?, + source_code, + &source_manager, + )?; + let library = assembler.clone().assemble_library([module])?; + Ok(library) +} + +#[tokio::main] +async fn main() -> Result<(), ClientError> { + // Initialize client + let endpoint = Endpoint::testnet(); + let timeout_ms = 10_000; + let rpc_client = Arc::new(GrpcClient::new(&endpoint, timeout_ms)); + + // Initialize keystore + let keystore_path = std::path::PathBuf::from("./keystore"); + let keystore = Arc::new(FilesystemKeyStore::::new(keystore_path).unwrap()); + + let store_path = std::path::PathBuf::from("./store.sqlite3"); + + let mut client = ClientBuilder::new() + .rpc(rpc_client) + .sqlite_store(store_path) + .authenticator(keystore.clone()) + .in_debug_mode(true.into()) + .build() + .await?; + + let sync_summary = client.sync_state().await.unwrap(); + println!("Latest block: {}", sync_summary.block_num); + + // ------------------------------------------------------------------------- + // STEP 1: Create a basic counter contract + // ------------------------------------------------------------------------- + println!("\n[STEP 1] Creating counter contract."); + + // Load the MASM file for the counter contract + let counter_path = Path::new("../masm/accounts/counter.masm"); + let counter_code = fs::read_to_string(counter_path).unwrap(); + + // Compile the account code into `AccountComponent` with one storage slot + let counter_component = AccountComponent::compile( + &counter_code, + TransactionKernel::assembler(), + vec![StorageSlot::Value(Word::default())], + ) + .unwrap() + .with_supports_all_types(); + + // Init seed for the counter contract + let mut seed = [0_u8; 32]; + client.rng().fill_bytes(&mut seed); + + // Build the new `Account` with the component + let counter_contract = AccountBuilder::new(seed) + .account_type(AccountType::RegularAccountImmutableCode) + .storage_mode(AccountStorageMode::Public) + .with_component(counter_component.clone()) + .with_auth_component(NoAuth) + .build() + .unwrap(); + + println!( + "counter_contract commitment: {:?}", + counter_contract.commitment() + ); + println!("counter_contract id: {:?}", counter_contract.id()); + println!("counter_contract storage: {:?}", counter_contract.storage()); + + client.add_account(&counter_contract, false).await.unwrap(); + + // ------------------------------------------------------------------------- + // STEP 2: Call the Counter Contract with a script + // ------------------------------------------------------------------------- + println!("\n[STEP 2] Call Counter Contract With Script"); + + // Load the MASM script referencing the increment procedure + let script_path = Path::new("../masm/scripts/counter_script.masm"); + let script_code = fs::read_to_string(script_path).unwrap(); + + // Create a library from the counter contract code + let assembler = TransactionKernel::assembler().with_debug_mode(true); + let account_component_lib = create_library( + assembler.clone(), + "external_contract::counter_contract", + &counter_code, + ) + .unwrap(); + + let tx_script = client + .script_builder() + .with_dynamically_linked_library(&account_component_lib) + .unwrap() + .compile_tx_script(&script_code) + .unwrap(); + + // Build a transaction request with the custom script + let tx_increment_request = TransactionRequestBuilder::new() + .custom_script(tx_script) + .build() + .unwrap(); + + // Execute and submit the transaction + let tx_id = client + .submit_new_transaction(counter_contract.id(), tx_increment_request) + .await + .unwrap(); + + println!( + "View transaction on MidenScan: https://testnet.midenscan.com/tx/{:?}", + tx_id + ); + + println!( + "Counter contract id: {:?}", + counter_contract.id().to_bech32(NetworkId::Testnet) + ); + + client.sync_state().await.unwrap(); + + // Retrieve updated contract data to see the incremented counter + let account = client.get_account(counter_contract.id()).await.unwrap(); + println!( + "counter contract storage: {:?}", + account.unwrap().account().storage().get_item(0) + ); + + Ok(()) +} +``` + +The output of our program will look something like this: + +```text +Latest block: 374255 + +[STEP 1] Creating counter contract. +one or more warnings were emitted +counter_contract commitment: Word([3964727668949550262, 4265714847747507878, 5784293172192015964, 16803438753763367241]) +counter_contract id: "mtst1qre73e6qcrfevqqngx8wewvveacqqjh8p2a" +counter_contract storage: AccountStorage { slots: [Value(Word([0, 0, 0, 0]))] } + +[STEP 2] Call Counter Contract With Script +Stack state before step 2610: +├── 0: 1 +├── 1: 0 +├── 2: 0 +├── 3: 0 +├── 4: 0 +├── 5: 0 +├── 6: 0 +├── 7: 0 +├── 8: 0 +├── 9: 0 +├── 10: 0 +├── 11: 0 +├── 12: 0 +├── 13: 0 +├── 14: 0 +├── 15: 0 +├── 16: 0 +├── 17: 0 +├── 18: 0 +└── 19: 0 + +└── (0 more items) + +View transaction on MidenScan: https://testnet.midenscan.com/tx/0x9767940bbed7bd3a74c24dc43f1ea8fe90a876dc7925621c217f648c63c4ab7a +counter contract storage: Ok(Word([0, 0, 0, 1])) +``` + +The line in the output `Stack state before step 2505` ouputs the stack state when we call "debug.stack" in the `counter.masm` file. + +To increment the count of the counter contract all you need is to know the account id of the counter and the procedure hash of the `increment_count` procedure. To increment the count without deploying the counter each time, you can modify the program above to hardcode the account id of the counter and the procedure hash of the `increment_count` prodedure in the masm script. + +### Running the example + +To run the full example, navigate to the `rust-client` directory in the [miden-tutorials](https://github.com/0xMiden/miden-tutorials/) repository and run this command: + +```bash +cd rust-client +cargo run --release --bin counter_contract_deploy +``` + +### Continue learning + +Next tutorial: [Interacting with Public Smart Contracts](public_account_interaction_tutorial.md) diff --git a/versioned_docs/version-0.12 (stable)/miden-tutorials/rust-client/create_deploy_tutorial.md b/versioned_docs/version-0.12 (stable)/miden-tutorials/rust-client/create_deploy_tutorial.md new file mode 100644 index 0000000..e0c46b7 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-tutorials/rust-client/create_deploy_tutorial.md @@ -0,0 +1,398 @@ +--- +title: "Creating Accounts and Faucets" +sidebar_position: 2 +--- + +# Creating Accounts and Faucets + +_Using the Miden client in Rust to create accounts and deploy faucets_ + +## Overview + +In this tutorial, we will create a Miden account for _Alice_ and deploy a fungible faucet. In the next section, we will mint tokens from the faucet to fund her account and transfer tokens from Alice's account to other Miden accounts. + +## What we'll cover + +- Understanding the differences between public and private accounts & notes +- Instantiating the Miden client +- Creating new accounts (public or private) +- Deploying a faucet to fund an account + +## Prerequisites + +Before you begin, ensure that a Miden node is running locally in a separate terminal window. To get the Miden node running locally, you can follow the instructions on the [Miden Node Setup](../miden_node_setup.md) page. + +## Public vs. private accounts & notes + +Before diving into coding, let's clarify the concepts of public and private accounts & notes on Miden: + +- Public accounts: The account's data and code are stored on-chain and are openly visible, including its assets. +- Private accounts: The account's state and logic are off-chain, only known to its owner. +- Public notes: The note's state is visible to anyone - perfect for scenarios where transparency is desired. +- Private notes: The note's state is stored off-chain, you will need to share the note data with the relevant parties (via email or Telegram) for them to be able to consume the note. + +Note: _The term "account" can be used interchangeably with the term "smart contract" since account abstraction on Miden is handled natively._ + +_It is useful to think of notes on Miden as "cryptographic cashier's checks" that allow users to send tokens. If the note is private, the note transfer is only known to the sender and receiver._ + +## Step 1: Initialize your repository + +Create a new Rust repository for your Miden project and navigate to it with the following command: + +```bash +cargo new miden-rust-client +cd miden-rust-client +``` + +Add the following dependencies to your `Cargo.toml` file: + +```toml +[dependencies] +miden-client = { version = "0.12", features = ["testing", "tonic"] } +miden-client-sqlite-store = { version = "0.12", package = "miden-client-sqlite-store" } +miden-lib = { version = "0.12", default-features = false } +miden-objects = { version = "0.12", default-features = false, features = ["testing"] } +miden-crypto = { version = "0.17.1", features = ["executable"] } +miden-assembly = "0.18.3" +rand = { version = "0.9" } +serde = { version = "1", features = ["derive"] } +serde_json = { version = "1.0", features = ["raw_value"] } +tokio = { version = "1.46", features = ["rt-multi-thread", "net", "macros", "fs"] } +rand_chacha = "0.9.0" +``` + +## Step 2: Initialize the client + +Before interacting with the Miden network, we must instantiate the client. In this step, we specify several parameters: + +- **RPC endpoint** - The URL of the Miden node you will connect to. +- **Client RNG** - The random number generator used by the client, ensuring that the serial number of newly created notes are unique. +- **SQLite Store** – An SQL database used by the client to store account and note data. +- **Authenticator** - The component responsible for generating transaction signatures. + +Copy and paste the following code into your `src/main.rs` file. + +```rust no_run +use miden_lib::account::auth::AuthRpoFalcon512; +use rand::{rngs::StdRng, RngCore}; +use std::sync::Arc; +use tokio::time::Duration; + +use miden_client::{ + account::{ + component::{BasicFungibleFaucet, BasicWallet}, + AccountId, + }, + address::NetworkId, + auth::AuthSecretKey, + builder::ClientBuilder, + keystore::FilesystemKeyStore, + note::{create_p2id_note, NoteType}, + rpc::{Endpoint, GrpcClient}, + transaction::{OutputNote, TransactionRequestBuilder}, + ClientError, +}; +use miden_client_sqlite_store::ClientBuilderSqliteExt; +use miden_objects::{ + account::{AccountBuilder, AccountIdVersion, AccountStorageMode, AccountType}, + asset::{FungibleAsset, TokenSymbol}, + Felt, +}; + +#[tokio::main] +async fn main() -> Result<(), ClientError> { + // Initialize client + let endpoint = Endpoint::testnet(); + let timeout_ms = 10_000; + let rpc_client = Arc::new(GrpcClient::new(&endpoint, timeout_ms)); + + // Initialize keystore + let keystore_path = std::path::PathBuf::from("./keystore"); + let keystore = Arc::new(FilesystemKeyStore::::new(keystore_path).unwrap()); + + let store_path = std::path::PathBuf::from("./store.sqlite3"); + + let mut client = ClientBuilder::new() + .rpc(rpc_client) + .sqlite_store(store_path) + .authenticator(keystore.clone()) + .in_debug_mode(true.into()) + .build() + .await?; + + let sync_summary = client.sync_state().await.unwrap(); + println!("Latest block: {}", sync_summary.block_num); + + Ok(()) +} +``` + +_When running the code above, there will be some unused imports, however, we will use these imports later on in the tutorial._ + +**Note**: Running the code above, will generate a `store.sqlite3` file and a `keystore` directory. The Miden client uses the `store.sqlite3` file to keep track of the state of accounts and notes. The `keystore` directory keeps track of private keys used by accounts. Be sure to add both to your `.gitignore`! + +Run the following command to execute `src/main.rs`: + +```bash +cargo run --release +``` + +After the program executes, you should see the latest block number printed to the terminal, for example: + +```text +Latest block number: 3855 +``` + +## Step 3: Creating a wallet + +Now that we've initialized the client, we can create a wallet for Alice. + +To create a wallet for Alice using the Miden client, we define the account type as mutable or immutable and specify whether it is public or private. A mutable wallet means you can change the account code after deployment. A wallet on Miden is simply an account with standardized code. + +In the example below we create a mutable public account for Alice. + +Add this snippet to the end of your file in the `main()` function: + +```rust ignore +//------------------------------------------------------------ +// STEP 1: Create a basic wallet for Alice +//------------------------------------------------------------ +println!("\n[STEP 1] Creating a new account for Alice"); + +// Account seed +let mut init_seed = [0_u8; 32]; +client.rng().fill_bytes(&mut init_seed); + +let key_pair = AuthSecretKey::new_rpo_falcon512(); + +// Build the account +let alice_account = AccountBuilder::new(init_seed) + .account_type(AccountType::RegularAccountUpdatableCode) + .storage_mode(AccountStorageMode::Public) + .with_auth_component(AuthRpoFalcon512::new(key_pair.public_key().to_commitment())) + .with_component(BasicWallet) + .build() + .unwrap(); + +// Add the account to the client +client.add_account(&alice_account, false).await?; + +// Add the key pair to the keystore +keystore.add_key(&key_pair).unwrap(); + +let alice_account_id_bech32 = alice_account.id().to_bech32(NetworkId::Testnet); +println!("Alice's account ID: {:?}", alice_account_id_bech32); +``` + +## Step 4: Deploying a fungible faucet + +To provide Alice with testnet assets, we must first deploy a faucet. A faucet account on Miden mints fungible tokens. + +We'll create a public faucet with a token symbol, decimals, and a max supply. We will use this faucet to mint tokens to Alice's account in the next section. + +Add this snippet to the end of your file in the `main()` function: + +```rust ignore +//------------------------------------------------------------ +// STEP 2: Deploy a fungible faucet +//------------------------------------------------------------ +println!("\n[STEP 2] Deploying a new fungible faucet."); + +// Faucet seed +let mut init_seed = [0u8; 32]; +client.rng().fill_bytes(&mut init_seed); + +// Faucet parameters +let symbol = TokenSymbol::new("MID").unwrap(); +let decimals = 8; +let max_supply = Felt::new(1_000_000); + +// Generate key pair +let key_pair = AuthSecretKey::new_rpo_falcon512(); + +// Build the faucet account +let faucet_account = AccountBuilder::new(init_seed) + .account_type(AccountType::FungibleFaucet) + .storage_mode(AccountStorageMode::Public) + .with_auth_component(AuthRpoFalcon512::new(key_pair.public_key().to_commitment())) + .with_component(BasicFungibleFaucet::new(symbol, decimals, max_supply).unwrap()) + .build() + .unwrap(); + +// Add the faucet to the client +client.add_account(&faucet_account, false).await?; + +// Add the key pair to the keystore +keystore.add_key(&key_pair).unwrap(); + +let faucet_account_id_bech32 = faucet_account.id().to_bech32(NetworkId::Testnet); +println!("Faucet account ID: {:?}", faucet_account_id_bech32); + +// Resync to show newly deployed faucet +client.sync_state().await?; +tokio::time::sleep(Duration::from_secs(2)).await; +``` + +_When tokens are minted from this faucet, each token batch is represented as a "note" (UTXO). You can think of a Miden Note as a cryptographic cashier's check that has certain spend conditions attached to it._ + +## Summary + +Your updated `main()` function in `src/main.rs` should look like this: + +```rust +use miden_lib::account::auth::AuthRpoFalcon512; +use rand::{rngs::StdRng, RngCore}; +use std::sync::Arc; +use tokio::time::Duration; + +use miden_client::{ + account::{ + component::{BasicFungibleFaucet, BasicWallet}, + AccountId, + }, + address::NetworkId, + auth::AuthSecretKey, + builder::ClientBuilder, + keystore::FilesystemKeyStore, + note::{create_p2id_note, NoteType}, + rpc::{Endpoint, GrpcClient}, + transaction::{OutputNote, TransactionRequestBuilder}, + ClientError, +}; +use miden_client_sqlite_store::ClientBuilderSqliteExt; +use miden_objects::{ + account::{AccountBuilder, AccountIdVersion, AccountStorageMode, AccountType}, + asset::{FungibleAsset, TokenSymbol}, + Felt, +}; + +#[tokio::main] +async fn main() -> Result<(), ClientError> { + // Initialize client + let endpoint = Endpoint::testnet(); + let timeout_ms = 10_000; + let rpc_client = Arc::new(GrpcClient::new(&endpoint, timeout_ms)); + + // Initialize keystore + let keystore_path = std::path::PathBuf::from("./keystore"); + let keystore = Arc::new(FilesystemKeyStore::::new(keystore_path).unwrap()); + + let store_path = std::path::PathBuf::from("./store.sqlite3"); + + let mut client = ClientBuilder::new() + .rpc(rpc_client) + .sqlite_store(store_path) + .authenticator(keystore.clone()) + .in_debug_mode(true.into()) + .build() + .await?; + + let sync_summary = client.sync_state().await.unwrap(); + println!("Latest block: {}", sync_summary.block_num); + + //------------------------------------------------------------ + // STEP 1: Create a basic wallet for Alice + //------------------------------------------------------------ + println!("\n[STEP 1] Creating a new account for Alice"); + + // Account seed + let mut init_seed = [0_u8; 32]; + client.rng().fill_bytes(&mut init_seed); + + let key_pair = AuthSecretKey::new_rpo_falcon512(); + + // Build the account + let alice_account = AccountBuilder::new(init_seed) + .account_type(AccountType::RegularAccountUpdatableCode) + .storage_mode(AccountStorageMode::Public) + .with_auth_component(AuthRpoFalcon512::new(key_pair.public_key().to_commitment())) + .with_component(BasicWallet) + .build() + .unwrap(); + + // Add the account to the client + client.add_account(&alice_account, false).await?; + + // Add the key pair to the keystore + keystore.add_key(&key_pair).unwrap(); + + let alice_account_id_bech32 = alice_account.id().to_bech32(NetworkId::Testnet); + println!("Alice's account ID: {:?}", alice_account_id_bech32); + + //------------------------------------------------------------ + // STEP 2: Deploy a fungible faucet + //------------------------------------------------------------ + println!("\n[STEP 2] Deploying a new fungible faucet."); + + // Faucet seed + let mut init_seed = [0u8; 32]; + client.rng().fill_bytes(&mut init_seed); + + // Faucet parameters + let symbol = TokenSymbol::new("MID").unwrap(); + let decimals = 8; + let max_supply = Felt::new(1_000_000); + + // Generate key pair + let key_pair = AuthSecretKey::new_rpo_falcon512(); + + // Build the faucet account + let faucet_account = AccountBuilder::new(init_seed) + .account_type(AccountType::FungibleFaucet) + .storage_mode(AccountStorageMode::Public) + .with_auth_component(AuthRpoFalcon512::new(key_pair.public_key().to_commitment())) + .with_component(BasicFungibleFaucet::new(symbol, decimals, max_supply).unwrap()) + .build() + .unwrap(); + + // Add the faucet to the client + client.add_account(&faucet_account, false).await?; + + // Add the key pair to the keystore + keystore.add_key(&key_pair).unwrap(); + + let faucet_account_id_bech32 = faucet_account.id().to_bech32(NetworkId::Testnet); + println!("Faucet account ID: {:?}", faucet_account_id_bech32); + + // Resync to show newly deployed faucet + client.sync_state().await?; + tokio::time::sleep(Duration::from_secs(2)).await; + + Ok(()) +} +``` + +Let's run the `src/main.rs` program again: + +```bash +cargo run --release +``` + +The output will look like this: + +```text +Latest block: 17771 + +[STEP 1] Creating a new account for Alice +Alice's account ID: "0x3cb3e596d14ad410000017901eaa7b" + +[STEP 2] Deploying a new fungible faucet. +Faucet account ID: "0x6ad1894ac233e4200000088311bb6b" +``` + +In this section we explained how to instantiate the Miden client, create a wallet account, and deploy a faucet. + +In the next section we will cover how to mint tokens from the faucet, consume notes, and send tokens to other accounts. + +### Running the example + +To run a full working example navigate to the `rust-client` directory in the [miden-tutorials](https://github.com/0xMiden/miden-tutorials/) repository and run this command: + +```bash +cd rust-client +cargo run --release --bin create_mint_consume_send +``` + +### Continue learning + +Next tutorial: [Mint, Consume, and Create Notes](mint_consume_create_tutorial.md) diff --git a/versioned_docs/version-0.12 (stable)/miden-tutorials/rust-client/creating_notes_in_masm_tutorial.md b/versioned_docs/version-0.12 (stable)/miden-tutorials/rust-client/creating_notes_in_masm_tutorial.md new file mode 100644 index 0000000..d647cc7 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-tutorials/rust-client/creating_notes_in_masm_tutorial.md @@ -0,0 +1,546 @@ +--- +title: "How to Create Notes in Miden Assembly" +sidebar_position: 11 +--- + +# Creating Notes in Miden Assembly + +_Creating notes inside the MidenVM using Miden assembly_ + +## Overview + +In this tutorial, we will create a custom note that generates a copy of itself when it is consumed by an account. The purpose of this tutorial is to demonstrate how to create notes inside the MidenVM using Miden assembly (MASM). By the end of this tutorial, you will understand how to write MASM code that creates notes. + +## What We'll Cover + +- Computing the note inputs commitment in MASM +- Creating notes in MASM + +## Prerequisites + +This tutorial assumes you have a basic understanding of Miden assembly and that you have completed the tutorial on [creating a custom note](./custom_note_how_to.md). + +## Why Creating Notes in MASM Is Useful + +Being able to create a note in MASM enables you to build various types of applications. Creating a note during the consumption of another note or from an account allows you to develop complex DeFi applications. + +Here are some tangible examples of when creating a note in MASM is useful in a DeFi context: + +- Creating snapshots of an account's state at a specific point in time (not possible in an EVM context) +- Representing partially fillable buy/sell orders as notes (SWAPP) +- Handling withdrawals from a smart contract + +## What We Will Be Building + +![Iterative Note Creation](../img/note_creation_masm.png) + +In the diagram above, note A is consumed by an account, and during the transaction, note A' is created. + +In this tutorial, we will create a note that contains an asset. When consumed, it outputs a copy of itself and allows the consuming account to take half of the asset. Although this type of note would not be used in a real-world context, it demonstrates several key concepts for writing MASM code that can create notes. + +## Step 1: Initialize Your Repository + +Create a new Rust repository for your Miden project and navigate to it with the following command: + +```bash +cargo new miden-project +cd miden-project +``` + +Add the following dependencies to your `Cargo.toml` file: + +```toml +[dependencies] +miden-client = { version = "0.12", features = ["testing", "tonic"] } +miden-client-sqlite-store = { version = "0.12", package = "miden-client-sqlite-store" } +miden-lib = { version = "0.12", default-features = false } +miden-objects = { version = "0.12", default-features = false, features = ["testing"] } +miden-crypto = { version = "0.17.1", features = ["executable"] } +miden-assembly = "0.18.3" +rand = { version = "0.9" } +serde = { version = "1", features = ["derive"] } +serde_json = { version = "1.0", features = ["raw_value"] } +tokio = { version = "1.46", features = ["rt-multi-thread", "net", "macros", "fs"] } +rand_chacha = "0.9.0" +``` + +## Step 2: Write the Note Script + +For better code organization, we will separate the Miden assembly code from our Rust code. + +Create a directory named `masm` at the **root** of your `miden-project` directory. This directory will contain our contract and MASM script code. + +Initialize the `masm` directory: + +```bash +mkdir masm/notes +``` + +This will create: + +```text +masm/ +└── notes/ +``` + +Inside the `masm/notes/` directory, create the file `iterative_output_note.masm`: + +```masm +use.miden::active_note +use.miden::tx +use.miden::note +use.miden::output_note +use.std::sys +use.std::crypto::hashes::rpo +use.miden::contracts::wallets::basic->wallet + +# Memory Addresses +const.ASSET=0 +const.ASSET_HALF=4 +const.ACCOUNT_ID_PREFIX=8 +const.ACCOUNT_ID_SUFFIX=9 +const.TAG=10 + +# => [] +begin + # Drop word if user accidentally pushes note_args + dropw + # => [] + + # Get note inputs + push.ACCOUNT_ID_PREFIX exec.active_note::get_inputs drop drop + # => [] + + # Get asset contained in note + push.ASSET exec.active_note::get_assets drop drop + # => [] + + mem_loadw_be.ASSET + # => [ASSET] + + # Compute half amount of asset + swap.3 push.2 div swap.3 + # => [ASSET_HALF] + + mem_storew_be.ASSET_HALF dropw + # => [] + + mem_loadw_be.ASSET + # => [ASSET] + + # Receive the entire asset amount to the wallet + call.wallet::receive_asset + # => [] + + # Get note inputs commitment + push.8.ACCOUNT_ID_PREFIX + # => [memory_address_pointer, number_of_inputs] + + # Note: Must pad with 0s to nearest multiple of 8 + exec.rpo::hash_memory + # => [INPUTS_COMMITMENT] + + # Push script hash + exec.active_note::get_script_root + # => [SCRIPT_HASH, INPUTS_COMMITMENT] + + # Get the current note serial number + exec.active_note::get_serial_number + # => [SERIAL_NUM, SCRIPT_HASH, INPUTS_COMMITMENT] + + # Increment serial number by 1 + push.1 add + # => [SERIAL_NUM+1, SCRIPT_HASH, INPUTS_COMMITMENT] + + exec.note::build_recipient_hash + # => [RECIPIENT] + + # Push hint, note type, and aux to stack + push.1.1.0 + # => [aux, public_note, execution_hint_always, RECIPIENT] + + # Load tag from memory + mem_load.TAG + # => [tag, aux, note_type, execution_hint, RECIPIENT] + + call.output_note::create + # => [note_idx, pad(15) ...] + + padw mem_loadw_be.ASSET_HALF + # => [ASSET / 2, note_idx] + + call.wallet::move_asset_to_note + # => [ASSET, note_idx, pad(11)] + + dropw drop + # => [] + + exec.sys::truncate_stack + # => [] +end +``` + +### How the Assembly Code Works: + +1. **Reads note inputs:** + The note begins by writing the note inputs to memory by calling the `note::get_inputs` procedure. It writes the note inputs starting at memory address 8, which is defined as the constant `ACCOUNT_ID_PREFIX`. +2. **Retrieving the asset:** + The note then calls `active_note::get_assets` to write the asset contained in the note to memory address 0, defined as `ASSET`. It computes half of the asset and stores the value at memory address 4, defined as `ASSET_HALF`. Finally, the note calls the `wallet::receive_asset` procedure to move the asset contained in the note to the consuming account. +3. **Computing note inputs hash in MASM:** + The script calls the `rpo::hash_memory` procedure with the number of inputs and the memory address where the inputs begin. This procedure returns the note inputs commitment. +4. **Getting the script hash:** + Next, the note script calls the `active_note::get_script_root` procedure, which returns the note's script hash. +5. **Getting the serial number for the future note:** + Although not strictly necessary in this scenario, preventing two identical notes from having the same serial number is important. If an account creates two identical notes with the same serial number, recipient, and asset vault, one of the notes may not be consumed. Therefore, the MASM code increments the serial number of the current note by 1. +6. **Computing the `RECIPIENT` hash:** + The `RECIPIENT` hash is defined as: + `hash(hash(hash(serial_num, [0; 4]), script_root), input_commitment)` + To compute it in MASM, the script calls the `note::build_recipient_hash` procedure with the serial number, script hash, and inputs commitment on the stack. +7. **Creating the note:** + To create the note, the script pushes the execution hint, note type, aux value, and tag onto the stack, then calls the `output_note::create` procedure, which returns a pointer to the note. +8. **Moving assets to the note:** + After the note is created, the script loads the half asset value computed in step 2 onto the stack and calls the `wallet::move_asset_to_note` procedure. +9. **Stack cleanup:** + Finally, the script cleans up the stack by calling `sys::truncate_stack` after creating the note and adding the assets. + +## Step 3: Rust Program + +With the Miden assembly note script written, we can move on to writing the Rust script to create and consume the note. + +Copy and paste the following code into your `src/main.rs` file. + +```rust +use miden_lib::account::auth::AuthRpoFalcon512; +use miden_lib::transaction::TransactionKernel; +use rand::{rngs::StdRng, RngCore}; +use std::{fs, path::Path, sync::Arc}; +use tokio::time::{sleep, Duration}; + +use miden_client::{ + account::{ + component::{BasicFungibleFaucet, BasicWallet}, + Account, + }, + address::NetworkId, + asset::{FungibleAsset, TokenSymbol}, + auth::AuthSecretKey, + builder::ClientBuilder, + crypto::FeltRng, + keystore::FilesystemKeyStore, + note::{ + Note, NoteAssets, NoteExecutionHint, NoteExecutionMode, NoteInputs, NoteMetadata, + NoteRecipient, NoteScript, NoteTag, NoteType, + }, + rpc::{Endpoint, GrpcClient}, + transaction::{OutputNote, TransactionRequestBuilder}, + Client, ClientError, Felt, +}; +use miden_client_sqlite_store::ClientBuilderSqliteExt; +use miden_objects::{ + account::{AccountBuilder, AccountStorageMode, AccountType}, + note::NoteDetails, +}; + +// Helper to create a basic account +async fn create_basic_account( + client: &mut Client>, + keystore: &Arc>, +) -> Result { + let mut init_seed = [0u8; 32]; + client.rng().fill_bytes(&mut init_seed); + + let key_pair = AuthSecretKey::new_rpo_falcon512(); + + let account = AccountBuilder::new(init_seed) + .account_type(AccountType::RegularAccountUpdatableCode) + .storage_mode(AccountStorageMode::Public) + .with_auth_component(AuthRpoFalcon512::new(key_pair.public_key().to_commitment())) + .with_component(BasicWallet) + .build() + .unwrap(); + + client.add_account(&account, false).await?; + keystore.add_key(&key_pair).unwrap(); + + Ok(account) +} + +async fn create_basic_faucet( + client: &mut Client>, + keystore: &Arc>, +) -> Result { + let mut init_seed = [0u8; 32]; + client.rng().fill_bytes(&mut init_seed); + + let key_pair = AuthSecretKey::new_rpo_falcon512(); + let symbol = TokenSymbol::new("MID").unwrap(); + let decimals = 8; + let max_supply = Felt::new(1_000_000); + + let account = AccountBuilder::new(init_seed) + .account_type(AccountType::FungibleFaucet) + .storage_mode(AccountStorageMode::Public) + .with_auth_component(AuthRpoFalcon512::new(key_pair.public_key().to_commitment())) + .with_component(BasicFungibleFaucet::new(symbol, decimals, max_supply).unwrap()) + .build() + .unwrap(); + + client.add_account(&account, false).await?; + keystore.add_key(&key_pair).unwrap(); + + Ok(account) +} + +// Helper to wait until an account has the expected number of consumable notes +async fn wait_for_notes( + client: &mut Client>, + account_id: &Account, + expected: usize, +) -> Result<(), ClientError> { + loop { + client.sync_state().await?; + let notes = client.get_consumable_notes(Some(account_id.id())).await?; + if notes.len() >= expected { + break; + } + println!( + "{} consumable notes found for account {}. Waiting...", + notes.len(), + account_id.id().to_bech32(NetworkId::Testnet) + ); + sleep(Duration::from_secs(3)).await; + } + Ok(()) +} + +#[tokio::main] +async fn main() -> Result<(), ClientError> { + // Initialize client + let endpoint = Endpoint::testnet(); + let timeout_ms = 10_000; + let rpc_client = Arc::new(GrpcClient::new(&endpoint, timeout_ms)); + + // Initialize keystore + let keystore_path = std::path::PathBuf::from("./keystore"); + let keystore = Arc::new(FilesystemKeyStore::::new(keystore_path).unwrap()); + + let store_path = std::path::PathBuf::from("./store.sqlite3"); + + let mut client = ClientBuilder::new() + .rpc(rpc_client) + .sqlite_store(store_path) + .authenticator(keystore.clone()) + .in_debug_mode(true.into()) + .build() + .await?; + + let sync_summary = client.sync_state().await.unwrap(); + println!("Latest block: {}", sync_summary.block_num); + + // ------------------------------------------------------------------------- + // STEP 1: Create accounts and deploy faucet + // ------------------------------------------------------------------------- + println!("\n[STEP 1] Creating new accounts"); + let alice_account = create_basic_account(&mut client, &keystore).await?; + println!( + "Alice's account ID: {:?}", + alice_account.id().to_bech32(NetworkId::Testnet) + ); + let bob_account = create_basic_account(&mut client, &keystore).await?; + println!( + "Bob's account ID: {:?}", + bob_account.id().to_bech32(NetworkId::Testnet) + ); + + println!("\nDeploying a new fungible faucet."); + let faucet = create_basic_faucet(&mut client, &keystore).await?; + println!( + "Faucet account ID: {:?}", + faucet.id().to_bech32(NetworkId::Testnet) + ); + client.sync_state().await?; + + // ------------------------------------------------------------------------- + // STEP 2: Mint tokens with P2ID + // ------------------------------------------------------------------------- + println!("\n[STEP 2] Mint tokens with P2ID"); + let faucet_id = faucet.id(); + let amount: u64 = 100; + let mint_amount = FungibleAsset::new(faucet_id, amount).unwrap(); + + let tx_req = TransactionRequestBuilder::new() + .build_mint_fungible_asset( + mint_amount, + alice_account.id(), + NoteType::Public, + client.rng(), + ) + .unwrap(); + + let tx_id = client.submit_new_transaction(faucet.id(), tx_req).await?; + println!("Minted tokens. TX: {:?}", tx_id); + + wait_for_notes(&mut client, &alice_account, 1).await?; + + // Consume the minted note + let consumable_notes = client + .get_consumable_notes(Some(alice_account.id())) + .await?; + + if let Some((note_record, _)) = consumable_notes.first() { + let consume_req = TransactionRequestBuilder::new() + .build_consume_notes(vec![note_record.id()]) + .unwrap(); + + let tx_id = client + .submit_new_transaction(alice_account.id(), consume_req) + .await?; + println!("Consumed minted note. TX: {:?}", tx_id); + } + + client.sync_state().await?; + + // ------------------------------------------------------------------------- + // STEP 3: Create iterative output note + // ------------------------------------------------------------------------- + println!("\n[STEP 3] Create iterative output note"); + + let assembler = TransactionKernel::assembler().with_debug_mode(true); + let code = fs::read_to_string(Path::new("../masm/notes/iterative_output_note.masm")).unwrap(); + let serial_num = client.rng().draw_word(); + + // Create note metadata and tag + let tag = NoteTag::for_public_use_case(0, 0, NoteExecutionMode::Local).unwrap(); + let metadata = NoteMetadata::new( + alice_account.id(), + NoteType::Public, + tag, + NoteExecutionHint::always(), + Felt::new(0), + )?; + let program = assembler.clone().assemble_program(&code).unwrap(); + let note_script = NoteScript::new(program); + let note_inputs = NoteInputs::new(vec![ + alice_account.id().prefix().as_felt(), + alice_account.id().suffix(), + tag.into(), + Felt::new(0), + ]) + .unwrap(); + + let recipient = NoteRecipient::new(serial_num, note_script.clone(), note_inputs.clone()); + let vault = NoteAssets::new(vec![mint_amount.into()])?; + let custom_note = Note::new(vault, metadata, recipient); + + let note_req = TransactionRequestBuilder::new() + .own_output_notes(vec![OutputNote::Full(custom_note.clone())]) + .build() + .unwrap(); + + let tx_id = client + .submit_new_transaction(alice_account.id(), note_req) + .await?; + println!( + "View transaction on MidenScan: https://testnet.midenscan.com/tx/{:?}", + tx_id + ); + + client.sync_state().await?; + + // ------------------------------------------------------------------------- + // STEP 4: Consume the iterative output note + // ------------------------------------------------------------------------- + println!("\n[STEP 4] Bob consumes the note and creates a copy"); + + // Increment the serial number for the new note + let serial_num_1 = [ + serial_num[0], + serial_num[1], + serial_num[2], + Felt::new(serial_num[3].as_int() + 1), + ] + .into(); + + // Reuse the note_script and note_inputs + let recipient = NoteRecipient::new(serial_num_1, note_script, note_inputs); + + // Note: Change metadata to include Bob's account as the creator + let metadata = NoteMetadata::new( + bob_account.id(), + NoteType::Public, + tag, + NoteExecutionHint::always(), + Felt::new(0), + )?; + + let asset_amount_1 = FungibleAsset::new(faucet_id, 50).unwrap(); + let vault = NoteAssets::new(vec![asset_amount_1.into()])?; + let output_note = Note::new(vault, metadata, recipient); + + let consume_custom_req = TransactionRequestBuilder::new() + .unauthenticated_input_notes([(custom_note, None)]) + .expected_future_notes(vec![( + NoteDetails::from(output_note.clone()), + output_note.metadata().tag(), + ) + .clone()]) + .expected_output_recipients(vec![output_note.recipient().clone()]) + .build() + .unwrap(); + + let tx_id = client + .submit_new_transaction(bob_account.id(), consume_custom_req) + .await?; + println!( + "Consumed Note Tx on MidenScan: https://testnet.midenscan.com/tx/{:?}", + tx_id + ); + + Ok(()) +} +``` + +Run the following command to execute `src/main.rs`: + +```bash +cargo run --release +``` + +The output will look something like this: + +```text +Latest block: 226933 + +[STEP 1] Creating new accounts +Alice's account ID: "mtst1qpljtarjtawzcyqqqdcqu53adytw09yw" +Bob's account ID: "mtst1qzaynsxth84vsyqqq0emse6ygcax3j59" + +Deploying a new fungible faucet. +Faucet account ID: "mtst1qpqpq6z8vrqvugqqqwjdnajgvurs9zgl" + +[STEP 2] Mint tokens with P2ID +0 consumable notes found for account mtst1qpljtarjtawzcyqqqdcqu53adytw09yw. Waiting... +0 consumable notes found for account mtst1qpljtarjtawzcyqqqdcqu53adytw09yw. Waiting... + +[STEP 3] Create iterative output note +View transaction on MidenScan: https://testnet.midenscan.com/tx/0x335061a434ccccbf9619052bdeacbc71b6e755b24f0ead0c3741a3b0954c78af + +[STEP 4] Bob consumes the note and creates a copy +Consumed Note Tx on MidenScan: https://testnet.midenscan.com/tx/0xa39acf2bb965b4669b91bf564e8aa2987a9fc86ee350cd159ad5db1054cb67ab +Account delta: AccountVaultDelta { fungible: FungibleAssetDelta({V0(Accoun +``` + +--- + +### Running the example + +To run the full example, navigate to the `rust-client` directory in the [miden-tutorials](https://github.com/0xMiden/miden-tutorials/) repository and run this command: + +```bash +cd rust-client +cargo run --release --bin note_creation_in_masm +``` + +### Continue learning + +Next tutorial: [Delegated Proving](./delegated_proving_tutorial.md) diff --git a/versioned_docs/version-0.12 (stable)/miden-tutorials/rust-client/custom_note_how_to.md b/versioned_docs/version-0.12 (stable)/miden-tutorials/rust-client/custom_note_how_to.md new file mode 100644 index 0000000..18b5008 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-tutorials/rust-client/custom_note_how_to.md @@ -0,0 +1,454 @@ +--- +title: "How To Create Notes with Custom Logic" +sidebar_position: 7 +--- + +# How to Create a Custom Note + +_Creating notes with custom logic_ + +## Overview + +In this guide, we will create a custom note on Miden that can only be consumed by someone who knows the preimage of the hash stored in the note. This approach securely embeds assets into the note and restricts spending to those who possess the correct secret number. + +By following the steps below and using the Miden Assembly code and Rust example, you will learn how to: + +- Create a note with custom logic. +- Leverage Miden’s privacy features to keep certain transaction details private. + +Unlike Ethereum, where all pending transactions are publicly visible in the mempool, Miden enables you to partially or completely hide transaction details. + +## What we'll cover + +- Writing Miden assembly for a note +- Consuming notes + +## Step-by-step process + +### 1. Creating two accounts: Alice & Bob + +First, we create two basic accounts for the two users: + +- **Alice:** The account that creates and funds the custom note. +- **Bob:** The account that will consume the note if they know the correct secret. + +### 2. Hashing the secret number + +The security of the custom note hinges on a secret number. Here, we will: + +- Choose a secret number (for example, an array of four integers). +- For simplicity, we're only hashing 4 elements. Therefore, we prepend an empty word—consisting of 4 zero integers—as a placeholder. This is required by the RPO hashing algorithm to ensure the input has the correct structure and length for proper processing. +- Compute the hash of the secret. The resulting hash will serve as the note’s input, meaning that the note can only be consumed if the secret number’s hash preimage is provided during consumption. + +### 3. Creating the custom note + +Now, combine the minted asset and the secret hash to build the custom note. The note is created using the following key steps: + +1. **Note Inputs:** + - The note is set up with the asset and the hash of the secret number as its input. +2. **Miden Assembly Code:** + - The Miden assembly note script ensures that the note can only be consumed if the provided secret, when hashed, matches the hash stored in the note input. + +Below is the Miden Assembly code for the note: + +```masm +use.miden::active_note +use.miden::contracts::wallets::basic->wallet + +# CONSTANTS +# ================================================================================================= + +const.EXPECTED_DIGEST_PTR=0 +const.ASSET_PTR=100 + +# ERRORS +# ================================================================================================= + +const.ERROR_DIGEST_MISMATCH="Expected digest does not match computed digest" + +#! Inputs (arguments): [HASH_PREIMAGE_SECRET] +#! Outputs: [] +#! +#! Note inputs are assumed to be as follows: +#! => EXPECTED_DIGEST +begin + # => HASH_PREIMAGE_SECRET + # Hashing the secret number + hash + # => [DIGEST] + + # Writing the note inputs to memory + push.EXPECTED_DIGEST_PTR exec.active_note::get_inputs drop drop + + # Pad stack and load expected digest from memory + padw push.EXPECTED_DIGEST_PTR mem_loadw_be + # => [EXPECTED_DIGEST, DIGEST] + + # Assert that the note input matches the digest + # Will fail if the two hashes do not match + assert_eqw.err=ERROR_DIGEST_MISMATCH + # => [] + + # --------------------------------------------------------------------------------------------- + # If the check is successful, we allow for the asset to be consumed + # --------------------------------------------------------------------------------------------- + + # Write the asset in note to memory address ASSET_PTR + push.ASSET_PTR exec.active_note::get_assets + # => [num_assets, dest_ptr] + + drop + # => [dest_ptr] + + # Load asset from memory + mem_loadw_be + # => [ASSET] + + # Call receive asset in wallet + call.wallet::receive_asset + # => [] +end +``` + +### How the assembly code works: + +1. **Constants and Error Handling:** + The code defines memory pointers (`EXPECTED_DIGEST_PTR` and `ASSET_PTR`) for better code organization and an error message for digest mismatches. +2. **Passing the Secret:** + The secret number is passed as `Note Arguments` into the note. +3. **Hashing the Secret:** + The `hash` instruction applies a hash permutation to the secret number, resulting in a digest that takes up four stack elements. +4. **Digest Comparison:** + The assembly code loads the expected digest from the note inputs stored in memory and compares it with the computed hash. If they don't match, the transaction fails with a clear error message. +5. **Asset Transfer:** + If the hash of the number passed in as `Note Arguments` matches the hash stored in the note inputs, the script continues, and the asset stored in the note is loaded from memory and passed to Bob's wallet via the `wallet::receive_asset` function. + +### 5. Consuming the note + +With the note created, Bob can now consume it—but only if he provides the correct secret. When Bob initiates the transaction to consume the note, he must supply the same secret number used when Alice created the note. The custom note’s logic will hash the secret and compare it with its stored hash. If they match, Bob’s wallet receives the asset. + +--- + +## Full Rust code example + +The following Rust code demonstrates how to implement the steps outlined above using the Miden client library: + +```rust +use miden_lib::account::auth::AuthRpoFalcon512; +use rand::{rngs::StdRng, RngCore}; +use std::{fs, path::Path, sync::Arc}; +use tokio::time::{sleep, Duration}; + +use miden_client::{ + account::{ + component::{BasicFungibleFaucet, BasicWallet}, + Account, + }, + address::NetworkId, + auth::AuthSecretKey, + builder::ClientBuilder, + crypto::FeltRng, + keystore::FilesystemKeyStore, + note::{ + Note, NoteAssets, NoteExecutionHint, NoteExecutionMode, NoteInputs, NoteMetadata, + NoteRecipient, NoteTag, NoteType, + }, + rpc::{Endpoint, GrpcClient}, + store::TransactionFilter, + transaction::{OutputNote, TransactionId, TransactionRequestBuilder, TransactionStatus}, + Client, ClientError, Felt, ScriptBuilder, +}; +use miden_client_sqlite_store::ClientBuilderSqliteExt; +use miden_objects::{ + account::{AccountBuilder, AccountStorageMode, AccountType}, + asset::{FungibleAsset, TokenSymbol}, + Hasher, +}; + +// Helper to create a basic account +async fn create_basic_account( + client: &mut Client>, + keystore: &Arc>, +) -> Result { + let mut init_seed = [0_u8; 32]; + client.rng().fill_bytes(&mut init_seed); + + let key_pair = AuthSecretKey::new_rpo_falcon512(); + + let account = AccountBuilder::new(init_seed) + .account_type(AccountType::RegularAccountUpdatableCode) + .storage_mode(AccountStorageMode::Public) + .with_auth_component(AuthRpoFalcon512::new(key_pair.public_key().to_commitment())) + .with_component(BasicWallet) + .build() + .unwrap(); + + client.add_account(&account, false).await?; + keystore.add_key(&key_pair).unwrap(); + + Ok(account) +} + +async fn create_basic_faucet( + client: &mut Client>, + keystore: &Arc>, +) -> Result { + let mut init_seed = [0u8; 32]; + client.rng().fill_bytes(&mut init_seed); + + let key_pair = AuthSecretKey::new_rpo_falcon512(); + let symbol = TokenSymbol::new("MID").unwrap(); + let decimals = 8; + let max_supply = Felt::new(1_000_000); + + let account = AccountBuilder::new(init_seed) + .account_type(AccountType::FungibleFaucet) + .storage_mode(AccountStorageMode::Public) + .with_auth_component(AuthRpoFalcon512::new(key_pair.public_key().to_commitment())) + .with_component(BasicFungibleFaucet::new(symbol, decimals, max_supply).unwrap()) + .build() + .unwrap(); + + client.add_account(&account, false).await?; + keystore.add_key(&key_pair).unwrap(); + + Ok(account) +} + +/// Waits for a specific transaction to be committed. +async fn wait_for_tx( + client: &mut Client>, + tx_id: TransactionId, +) -> Result<(), ClientError> { + loop { + client.sync_state().await?; + + // Check transaction status + let txs = client + .get_transactions(TransactionFilter::Ids(vec![tx_id])) + .await?; + let tx_committed = if !txs.is_empty() { + matches!(txs[0].status, TransactionStatus::Committed { .. }) + } else { + false + }; + + if tx_committed { + println!("✅ transaction {} committed", tx_id.to_hex()); + break; + } + + println!( + "Transaction {} not yet committed. Waiting...", + tx_id.to_hex() + ); + sleep(Duration::from_secs(2)).await; + } + Ok(()) +} + +#[tokio::main] +async fn main() -> Result<(), ClientError> { + // Initialize client + let endpoint = Endpoint::testnet(); + let timeout_ms = 10_000; + let rpc_client = Arc::new(GrpcClient::new(&endpoint, timeout_ms)); + + // Initialize keystore + let keystore_path = std::path::PathBuf::from("./keystore"); + let keystore = Arc::new(FilesystemKeyStore::::new(keystore_path).unwrap()); + + let store_path = std::path::PathBuf::from("./store.sqlite3"); + + let mut client = ClientBuilder::new() + .rpc(rpc_client) + .sqlite_store(store_path) + .authenticator(keystore.clone()) + .in_debug_mode(true.into()) + .build() + .await?; + + let sync_summary = client.sync_state().await.unwrap(); + println!("Latest block: {}", sync_summary.block_num); + + // ------------------------------------------------------------------------- + // STEP 1: Create accounts and deploy faucet + // ------------------------------------------------------------------------- + println!("\n[STEP 1] Creating new accounts"); + let alice_account = create_basic_account(&mut client, &keystore).await?; + println!( + "Alice's account ID: {:?}", + alice_account.id().to_bech32(NetworkId::Testnet) + ); + let bob_account = create_basic_account(&mut client, &keystore).await?; + println!( + "Bob's account ID: {:?}", + bob_account.id().to_bech32(NetworkId::Testnet) + ); + + println!("\nDeploying a new fungible faucet."); + let faucet = create_basic_faucet(&mut client, &keystore).await?; + println!( + "Faucet account ID: {:?}", + faucet.id().to_bech32(NetworkId::Testnet) + ); + client.sync_state().await?; + + // ------------------------------------------------------------------------- + // STEP 2: Mint tokens with P2ID + // ------------------------------------------------------------------------- + println!("\n[STEP 2] Mint tokens with P2ID"); + let faucet_id = faucet.id(); + let amount: u64 = 100; + let mint_amount = FungibleAsset::new(faucet_id, amount).unwrap(); + let tx_request = TransactionRequestBuilder::new() + .build_mint_fungible_asset( + mint_amount, + alice_account.id(), + NoteType::Public, + client.rng(), + ) + .unwrap(); + + let tx_id = client + .submit_new_transaction(faucet.id(), tx_request) + .await?; + println!("Minted tokens. TX: {:?}", tx_id); + + // Wait for the note to be available + client.sync_state().await?; + wait_for_tx(&mut client, tx_id).await?; + + // Consume the minted note + let consumable_notes = client + .get_consumable_notes(Some(alice_account.id())) + .await?; + + if let Some((note_record, _)) = consumable_notes.first() { + let consume_request = TransactionRequestBuilder::new() + .build_consume_notes(vec![note_record.id()]) + .unwrap(); + + let tx_id = client + .submit_new_transaction(alice_account.id(), consume_request) + .await?; + println!("Consumed minted note. TX: {:?}", tx_id); + } + + client.sync_state().await?; + + // ------------------------------------------------------------------------- + // STEP 3: Create custom note + // ------------------------------------------------------------------------- + println!("\n[STEP 3] Create custom note"); + let secret_vals = vec![Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)]; + let digest = Hasher::hash_elements(&secret_vals); + println!("digest: {:?}", digest); + + let code = fs::read_to_string(Path::new("../masm/notes/hash_preimage_note.masm")).unwrap(); + let serial_num = client.rng().draw_word(); + + let note_script = ScriptBuilder::new(true).compile_note_script(code).unwrap(); + let note_inputs = NoteInputs::new(digest.to_vec()).unwrap(); + let recipient = NoteRecipient::new(serial_num, note_script, note_inputs); + let tag = NoteTag::for_public_use_case(0, 0, NoteExecutionMode::Local).unwrap(); + let metadata = NoteMetadata::new( + alice_account.id(), + NoteType::Public, + tag, + NoteExecutionHint::always(), + Felt::new(0), + )?; + let vault = NoteAssets::new(vec![mint_amount.into()])?; + let custom_note = Note::new(vault, metadata, recipient); + println!("note hash: {:?}", custom_note.id().to_hex()); + + let note_request = TransactionRequestBuilder::new() + .own_output_notes(vec![OutputNote::Full(custom_note.clone())]) + .build() + .unwrap(); + + let tx_id = client + .submit_new_transaction(alice_account.id(), note_request) + .await?; + println!( + "View transaction on MidenScan: https://testnet.midenscan.com/tx/{:?}", + tx_id + ); + + client.sync_state().await?; + + // ------------------------------------------------------------------------- + // STEP 4: Consume the Custom Note + // ------------------------------------------------------------------------- + println!("\n[STEP 4] Bob consumes the Custom Note with Correct Secret"); + + let secret = [Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)]; + let consume_custom_request = TransactionRequestBuilder::new() + .unauthenticated_input_notes([(custom_note, Some(secret.into()))]) + .build() + .unwrap(); + + let tx_id = client + .submit_new_transaction(bob_account.id(), consume_custom_request) + .await?; + println!( + "Consumed Note Tx on MidenScan: https://testnet.midenscan.com/tx/{:?} \n", + tx_id + ); + + Ok(()) +} +``` + +The output of our program will look something like this: + +```text +Latest block: 226943 + +[STEP 1] Creating new accounts +Alice's account ID: "mtst1qqufkq3xr0rr5yqqqwgrc20ctythccy6" +Bob's account ID: "mtst1qz76c9fvhvms2yqqqvvw8tf6m5h86y2h" + +Deploying a new fungible faucet. +Faucet account ID: "mtst1qpwsgjstpwvykgqqqwwzgz3u5vwuuywe" + +[STEP 2] Mint tokens with P2ID +Note 0x88d8c4a50c0e6342e58026b051fb6038867de21d3bd3963aec67fd6c45861faf not found. Waiting... +Note 0x88d8c4a50c0e6342e58026b051fb6038867de21d3bd3963aec67fd6c45861faf not found. Waiting... +✅ note found 0x88d8c4a50c0e6342e58026b051fb6038867de21d3bd3963aec67fd6c45861faf + +[STEP 3] Create custom note +digest: RpoDigest([14371582251229115050, 1386930022051078873, 17689831064175867466, 9632123050519021080]) +note hash: "0x14c66143377223e090e5b4da0d1e5ce6c6521622ad5b92161a704a25c915769b" +View transaction on MidenScan: https://testnet.midenscan.com/tx/0xffbee228a2c6283efe958c6b3cd31af88018c029221b413b0f23fcfacb2cb611 + +[STEP 4] Bob consumes the Custom Note with Correct Secret +Consumed Note Tx on MidenScan: https://testnet.midenscan.com/tx/0xe6c8bb7b469e03dcacd8f1f400011a781e96ad4266ede11af8e711379e85b929 + +account delta: AccountVaultDelta { fungible: FungibleAssetDelta({V0(AccountIdV0 { prefix: 6702563556733766432, suffix: 1016103534633728 }): 100}), non_fungible: NonFungibleAssetDelta({}) } +``` + +## Conclusion + +You have now seen how to create a custom note on Miden that requires a secret preimage to be consumed. We covered: + +1. Creating and funding accounts (Alice and Bob) +2. Hashing a secret number +3. Building a note with custom logic in Miden Assembly +4. Consuming the note by providing the correct secret + +By leveraging Miden’s privacy features, you can create customized logic for secure asset transfers that depend on keeping parts of the transaction private. + +### Running the example + +To run the custom note example, navigate to the `rust-client` directory in the [miden-tutorials](https://github.com/0xMiden/miden-tutorials/) repository and run this command: + +```bash +cd rust-client +cargo run --release --bin hash_preimage_note +``` + +### Continue learning + +Next tutorial: [How to Use Unauthenticated Notes](unauthenticated_note_how_to.md) diff --git a/versioned_docs/version-0.12 (stable)/miden-tutorials/rust-client/delegated_proving_tutorial.md b/versioned_docs/version-0.12 (stable)/miden-tutorials/rust-client/delegated_proving_tutorial.md new file mode 100644 index 0000000..53f6893 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-tutorials/rust-client/delegated_proving_tutorial.md @@ -0,0 +1,207 @@ +--- +title: "Delegated Proving" +sidebar_position: 12 +--- + +# Delegated Proving + +_Using delegated proving to minimize transaction proving times on computationally constrained devices_ + +## Overview + +In this tutorial we will cover how to use delegated proving with the Miden Rust client to minimize the time it takes to generate a valid transaction proof. In the code below, we will create an account, mint tokens from a faucet, then send the tokens to another account using delegated proving. + +## Prerequisites + +This tutorial assumes you have basic familiarity with the Miden Rust client. + +## What we'll cover + +- Explaining what "delegated proving" is and its pros and cons +- How to use delegated proving with the Rust client + +## What is Delegated Proving? + +Before diving into our code example, let's clarify what "delegated proving" means. + +Delegated proving is the process of outsourcing the ZK proof generation of your transaction to a third party. For certain computationally constrained devices such as mobile phones and web browser environments, generating ZK proofs might take too long to ensure an acceptable user experience. Devices that do not have the computational resources to generate Miden proofs in under 1-2 seconds can use delegated proving to provide a more responsive user experience. + +_How does it work?_ When a user choses to use delegated proving, they send off their locally executed transaction to a dedicated server. This dedicated server generates the ZK proof for the executed transaction and sends the proof back to the user. Proving a transaction with delegated proving is trustless, meaning if the delegated prover is malicious, they could not compromise the security of the account that is submitting a transaction to be processed by the delegated prover. + +The only downside of using delegated proving is that it reduces the privacy of the account that uses delegated proving, because the delegated prover would have knowledge of the inputs to the transaction that is being proven. For example, it would not be advisable to use delegated proving in the case of our "How to Create a Custom Note" tutorial, since the note we create requires knowledge of a hash preimage to redeem the assets in the note. Using delegated proving would reveal the hash preimage to the server running the delegated proving service. + +Anyone can run their own delegated prover server. If you are building a product on Miden, it may make sense to run your own delegated prover server for your users. To run your own delegated proving server, follow the instructions here: https://crates.io/crates/miden-remote-prover. + +## Step 1: Initialize your repository + +Create a new Rust repository for your Miden project and navigate to it with the following command: + +```bash +cargo new miden-delegated-proving-app +cd miden-delegated-proving-app +``` + +Add the following dependencies to your `Cargo.toml` file: + +```toml +[dependencies] +miden-client = { version = "0.12", features = ["testing", "tonic"] } +miden-client-sqlite-store = { version = "0.12", package = "miden-client-sqlite-store" } +miden-lib = { version = "0.12", default-features = false } +miden-objects = { version = "0.12", default-features = false, features = ["testing"] } +miden-crypto = { version = "0.17.1", features = ["executable"] } +miden-assembly = "0.18.3" +rand = { version = "0.9" } +serde = { version = "1", features = ["derive"] } +serde_json = { version = "1.0", features = ["raw_value"] } +tokio = { version = "1.46", features = ["rt-multi-thread", "net", "macros", "fs"] } +rand_chacha = "0.9.0" +``` + +## Step 2: Initialize the client and delegated prover endpoint and construct transactions + +Similarly to previous tutorials, we must instantiate the client. +We construct a `RemoteTransactionProver` that points to our delegated-proving service running at https://tx-prover.testnet.miden.io. + +```rust +use miden_client::auth::AuthSecretKey; +use miden_lib::account::auth::AuthRpoFalcon512; +use rand::{rngs::StdRng, RngCore}; +use std::sync::Arc; + +use miden_client::{ + account::component::BasicWallet, + builder::ClientBuilder, + keystore::FilesystemKeyStore, + rpc::{Endpoint, GrpcClient}, + transaction::{TransactionProver, TransactionRequestBuilder}, + ClientError, RemoteTransactionProver, +}; +use miden_client_sqlite_store::ClientBuilderSqliteExt; +use miden_objects::account::{AccountBuilder, AccountStorageMode, AccountType}; + +#[tokio::main] +async fn main() -> Result<(), ClientError> { + // Initialize client + let endpoint = Endpoint::testnet(); + let timeout_ms = 10_000; + let rpc_client = Arc::new(GrpcClient::new(&endpoint, timeout_ms)); + + // Initialize keystore + let keystore_path = std::path::PathBuf::from("./keystore"); + let keystore = Arc::new(FilesystemKeyStore::::new(keystore_path).unwrap()); + + let store_path = std::path::PathBuf::from("./store.sqlite3"); + + let mut client = ClientBuilder::new() + .rpc(rpc_client) + .sqlite_store(store_path) + .authenticator(keystore.clone()) + .in_debug_mode(true.into()) + .build() + .await?; + + let sync_summary = client.sync_state().await.unwrap(); + println!("Latest block: {}", sync_summary.block_num); + + // Create Alice's account + let mut init_seed = [0_u8; 32]; + client.rng().fill_bytes(&mut init_seed); + + let key_pair = AuthSecretKey::new_rpo_falcon512(); + + let alice_account = AccountBuilder::new(init_seed) + .account_type(AccountType::RegularAccountImmutableCode) + .storage_mode(AccountStorageMode::Private) + .with_auth_component(AuthRpoFalcon512::new(key_pair.public_key().to_commitment())) + .with_component(BasicWallet) + .build() + .unwrap(); + + client.add_account(&alice_account, false).await?; + keystore.add_key(&key_pair).unwrap(); + + // ------------------------------------------------------------------------- + // Setup the remote tx prover + // ------------------------------------------------------------------------- + let remote_tx_prover: RemoteTransactionProver = + RemoteTransactionProver::new("https://tx-prover.testnet.miden.io"); + let tx_prover: Arc = Arc::new(remote_tx_prover); + + // We use a dummy transaction request to showcase delegated proving. + // The only effect of this tx should be increasing Alice's nonce. + println!("Alice nonce initial: {:?}", alice_account.nonce()); + let script_code = "begin push.1 drop end"; + let tx_script = client + .script_builder() + .compile_tx_script(script_code) + .unwrap(); + + let transaction_request = TransactionRequestBuilder::new() + .custom_script(tx_script) + .build() + .unwrap(); + + // Step 1: Execute the transaction locally + println!("Executing transaction..."); + let tx_result = client + .execute_transaction(alice_account.id(), transaction_request) + .await?; + + // Step 2: Prove the transaction using the remote prover + println!("Proving transaction with remote prover..."); + let proven_transaction = client.prove_transaction_with(&tx_result, tx_prover).await?; + + // Step 3: Submit the proven transaction + println!("Submitting proven transaction..."); + let submission_height = client + .submit_proven_transaction(proven_transaction, &tx_result) + .await?; + + // Step 4: Apply the transaction to local store + client + .apply_transaction(&tx_result, submission_height) + .await?; + + println!("Transaction submitted successfully using delegated prover!"); + + client.sync_state().await.unwrap(); + + let account = client + .get_account(alice_account.id()) + .await + .unwrap() + .unwrap(); + + println!("Alice nonce has increased: {:?}", account.account().nonce()); + + Ok(()) +} +``` + +Now let's run the `src/main.rs` program: + +```bash +cargo run --release +``` + +The output will look like this: + +```text +Latest block: 226954 +Alice initial account balance: Ok(1000) +Alice final account balance: Ok(900) +``` + +### Running the example + +To run a full working example navigate to the `rust-client` directory in the [miden-tutorials](https://github.com/0xMiden/miden-tutorials/) repository and run this command: + +```bash +cd rust-client +cargo run --release --bin delegated_prover +``` + +### Continue learning + +Next tutorial: [Consuming On-Chain Price Data from the Pragma Oracle](oracle_tutorial.md) diff --git a/versioned_docs/version-0.12 (stable)/miden-tutorials/rust-client/foreign_procedure_invocation_tutorial.md b/versioned_docs/version-0.12 (stable)/miden-tutorials/rust-client/foreign_procedure_invocation_tutorial.md new file mode 100644 index 0000000..540b24f --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-tutorials/rust-client/foreign_procedure_invocation_tutorial.md @@ -0,0 +1,746 @@ +--- +title: "Foreign Procedure Invocation" +sidebar_position: 7 +--- + +# Foreign Procedure Invocation Tutorial + +_Using foreign procedure invocation to craft read-only cross-contract calls in the Miden VM_ + +## Overview + +In previous tutorials we deployed a public counter contract and incremented the count from a different client instance. + +In this tutorial we will cover the basics of "foreign procedure invocation" (FPI) in the Miden VM. To demonstrate FPI, we will build a "count copy" smart contract that reads the count from our previously deployed counter contract and copies the count to its own local storage. + +Foreign procedure invocation (FPI) is a powerful tool for building smart contracts in the Miden VM. FPI allows one smart contract to call "read-only" procedures in other smart contracts. + +The term "foreign procedure invocation" might sound a bit verbose, but it is as simple as one smart contract calling a non-state modifying procedure in another smart contract. The "EVM equivalent" of foreign procedure invocation would be a smart contract calling a read-only function in another contract. + +FPI is useful for developing smart contracts that extend the functionality of existing contracts on Miden. FPI is the core primitive used by price oracles on Miden. + +## What We Will Build + +![count copy FPI diagram](../img/count_copy_fpi_diagram.png) + +The diagram above depicts the "count copy" smart contract using foreign procedure invocation to read the count state of the counter contract. After reading the state via FPI, the "count copy" smart contract writes the value returned from the counter contract to storage. + +## What we'll cover + +- Foreign Procedure Invocation (FPI) +- Building a "count copy" Smart Contract + +## Prerequisites + +This tutorial assumes you have a basic understanding of Miden assembly and completed the previous tutorial on deploying the counter contract. We will be working within the same `miden-counter-contract` repository that we created in the [Interacting with Public Smart Contracts](./public_account_interaction_tutorial.md) tutorial. + +## Step 1: Set up your repository + +We will be using the same repository used in the "Interacting with Public Smart Contracts" tutorial. To set up your repository for this tutorial, first follow up until step two [here](./public_account_interaction_tutorial.md). + +## Step 2: Set up the "count reader" contract + +Inside of the `masm/accounts/` directory, create the `count_reader.masm` file. This is the smart contract that will read the "count" value from the counter contract. + +`masm/accounts/count_reader.masm`: + +```masm +use.miden::active_account +use.miden::native_account +use.miden::tx +use.std::sys + +# => [account_id_prefix, account_id_suffix, get_count_proc_hash] +export.copy_count + exec.tx::execute_foreign_procedure + # => [count] + + push.0 + # [index, count] + + debug.stack + + exec.native_account::set_item dropw + # => [] + + exec.sys::truncate_stack + # => [] +end +``` + +In the count reader smart contract we have a `copy_count` procedure that uses `tx::execute_foreign_procedure` to call the `get_count` procedure in the counter contract. + +To call the `get_count` procedure, we push its hash along with the counter contract's ID suffix and prefix. + +This is what the stack state should look like before we call `tx::execute_foreign_procedure`: + +```text +# => [account_id_prefix, account_id_suffix, GET_COUNT_HASH] +``` + +After calling the `get_count` procedure in the counter contract, we call `debug.stack` and then save the count of the counter contract to index 0 in storage. + +**Note**: _The bracket symbols used in the count copy contract are not valid MASM syntax. These are simply placeholder elements that we will replace with the actual values before compilation._ + +Inside the `masm/scripts/` directory, create the `reader_script.masm` file: + +```masm +use.external_contract::count_reader_contract +use.std::sys + +begin + push.{get_count_proc_hash} + # => [GET_COUNT_HASH] + + push.{account_id_suffix} + # => [account_id_suffix, GET_COUNT_HASH] + + push.{account_id_prefix} + # => [account_id_prefix, account_id_suffix, GET_COUNT_HASH] + + call.count_reader_contract::copy_count + # => [] + + exec.sys::truncate_stack + # => [] +end +``` + +**Note**: _`push.{get_count_proc_hash}` is not valid MASM, we will format the string with the value get_count_proc_hash before passing this script code to the assembler._ + +### Step 3: Set up your `src/main.rs` file: + +```rust no_run +use miden_crypto::Felt; +use miden_lib::account::auth::NoAuth; +use miden_lib::transaction::TransactionKernel; +use rand::{rngs::StdRng, RngCore}; +use std::{fs, path::Path, sync::Arc, time::Duration}; +use tokio::time::sleep; + +use miden_client::{ + assembly::{Assembler, DefaultSourceManager, LibraryPath, Module, ModuleKind}, + builder::ClientBuilder, + keystore::FilesystemKeyStore, + rpc::{domain::account::AccountStorageRequirements, Endpoint, GrpcClient}, + transaction::{ForeignAccount, TransactionRequestBuilder}, + ClientError, +}; +use miden_client_sqlite_store::ClientBuilderSqliteExt; +use miden_objects::{ + account::{ + AccountBuilder, AccountComponent, AccountId, AccountStorageMode, AccountType, StorageSlot, + }, + assembly::mast::MastNodeExt, + Word, +}; + +fn create_library( + assembler: Assembler, + library_path: &str, + source_code: &str, +) -> Result> { + let source_manager = Arc::new(DefaultSourceManager::default()); + let module = Module::parser(ModuleKind::Library).parse_str( + LibraryPath::new(library_path)?, + source_code, + &source_manager, + )?; + let library = assembler.clone().assemble_library([module])?; + Ok(library) +} + +#[tokio::main] +async fn main() -> Result<(), ClientError> { + // Initialize client + let endpoint = Endpoint::testnet(); + let timeout_ms = 10_000; + let rpc_client = Arc::new(GrpcClient::new(&endpoint, timeout_ms)); + + // Initialize keystore + let keystore_path = std::path::PathBuf::from("./keystore"); + let keystore = Arc::new(FilesystemKeyStore::::new(keystore_path).unwrap()); + + let store_path = std::path::PathBuf::from("./store.sqlite3"); + + let mut client = ClientBuilder::new() + .rpc(rpc_client) + .sqlite_store(store_path) + .authenticator(keystore.clone()) + .in_debug_mode(true.into()) + .build() + .await?; + + let sync_summary = client.sync_state().await.unwrap(); + println!("Latest block: {}", sync_summary.block_num); + + // ------------------------------------------------------------------------- + // STEP 1: Create the Count Reader Contract + // ------------------------------------------------------------------------- + println!("\n[STEP 1] Creating count reader contract."); + + // Load the MASM file for the counter contract + let count_reader_path = Path::new("../masm/accounts/count_reader.masm"); + let count_reader_code = fs::read_to_string(count_reader_path).unwrap(); + + // Prepare assembler (debug mode = true) + let assembler = TransactionKernel::assembler().with_debug_mode(true); + + // Compile the account code into `AccountComponent` with one storage slot + let count_reader_component = AccountComponent::compile( + &count_reader_code, + TransactionKernel::assembler(), + vec![StorageSlot::Value(Word::default())], + ) + .unwrap() + .with_supports_all_types(); + + // Init seed for the counter contract + let mut init_seed = [0_u8; 32]; + client.rng().fill_bytes(&mut init_seed); + + // Build the new `Account` with the component + let count_reader_contract = AccountBuilder::new(init_seed) + .account_type(AccountType::RegularAccountImmutableCode) + .storage_mode(AccountStorageMode::Public) + .with_component(count_reader_component.clone()) + .with_auth_component(NoAuth) + .build() + .unwrap(); + + println!( + "count_reader hash: {:?}", + count_reader_contract.commitment() + ); + println!("contract id: {:?}", count_reader_contract.id()); + + client + .add_account(&count_reader_contract, false) + .await + .unwrap(); + + Ok(()) +} +``` + +Run the following command to execute src/main.rs: + +```bash +cargo run --release +``` + +The output of our program will look something like this: + +```text +Latest block: 226976 + +[STEP 1] Creating count reader contract. +count_reader hash: RpoDigest([15888177100833057221, 15548657445961063290, 5580812380698193124, 9604096693288041818]) +contract id: "mtst1qp3ca3adt34euqqqqwt488x34qnnd495" +``` + +## Step 4: Build and read the state of the counter contract deployed on testnet + +Add this snippet to the end of your file in the `main()` function that we created in the previous step: + +```rust ignore +// ------------------------------------------------------------------------- +// STEP 2: Build & Get State of the Counter Contract +// ------------------------------------------------------------------------- +println!("\n[STEP 2] Building counter contract from public state"); + +// Define the Counter Contract account id from counter contract deploy +let (_, counter_contract_id) = + AccountId::from_bech32("mtst1arjemrxne8lj5qz4mg9c8mtyxg954483").unwrap(); + +println!("counter contract id: {:?}", counter_contract_id); + +client + .import_account_by_id(counter_contract_id) + .await + .unwrap(); + +let counter_contract_details = client.get_account(counter_contract_id).await.unwrap(); + +let counter_contract = if let Some(account_record) = counter_contract_details { + // Clone the account to get an owned instance + let account = account_record.account().clone(); + println!( + "Account details: {:?}", + account.storage().slots().first().unwrap() + ); + account // Now returns an owned account +} else { + panic!("Counter contract not found!"); +}; +``` + +This step uses the logic we explained in the [Public Account Interaction Tutorial](./public_account_interaction_tutorial.md) to read the state of the Counter contract and import it to the client locally. + +## Step 5: Call the counter contract via foreign procedure invocation + +Add this snippet to the end of your file in the `main()` function: + +```rust ignore +// ------------------------------------------------------------------------- +// STEP 3: Call the Counter Contract via Foreign Procedure Invocation (FPI) +// ------------------------------------------------------------------------- +println!("\n[STEP 3] Call counter contract with FPI from count copy contract"); + +let counter_contract_path = Path::new("../masm/accounts/counter.masm"); +let counter_contract_code = fs::read_to_string(counter_contract_path).unwrap(); + +let counter_contract_component = AccountComponent::compile( + &counter_contract_code, + TransactionKernel::assembler(), + vec![], +) +.unwrap() +.with_supports_all_types(); + +// Getting the hash of the `get_count` procedure +let get_proc_export = counter_contract_component + .library() + .exports() + .find(|export| export.name.name.as_str() == "get_count") + .unwrap(); + +let get_proc_mast_id = counter_contract_component + .library() + .get_export_node_id(&get_proc_export.name); + +let get_count_hash = counter_contract_component + .library() + .mast_forest() + .get_node_by_id(get_proc_mast_id) + .unwrap() + .digest() + .as_elements() + .iter() + .map(|f: &Felt| format!("{}", f.as_int())) + .collect::>() + .join("."); + +println!("get count hash: {:?}", get_count_hash); +println!("counter id prefix: {:?}", counter_contract.id().prefix()); +println!("suffix: {:?}", counter_contract.id().suffix()); + +// Build the script that calls the count_copy_contract +let script_path = Path::new("../masm/scripts/reader_script.masm"); +let script_code_original = fs::read_to_string(script_path).unwrap(); +let script_code = script_code_original + .replace("{get_count_proc_hash}", &get_count_hash) + .replace( + "{account_id_suffix}", + &counter_contract.id().suffix().to_string(), + ) + .replace( + "{account_id_prefix}", + &counter_contract.id().prefix().to_string(), + ); + +let account_component_lib = create_library( + assembler.clone(), + "external_contract::count_reader_contract", + &count_reader_code, +) +.unwrap(); + +let tx_script = client + .script_builder() + .with_dynamically_linked_library(&account_component_lib) + .unwrap() + .compile_tx_script(&script_code) + .unwrap(); + +let foreign_account = + ForeignAccount::public(counter_contract_id, AccountStorageRequirements::default()).unwrap(); + +// Build a transaction request with the custom script +let tx_request = TransactionRequestBuilder::new() + .foreign_accounts([foreign_account]) + .custom_script(tx_script) + .build() + .unwrap(); + +// Execute and submit the transaction +let tx_id = client + .submit_new_transaction(count_reader_contract.id(), tx_request) + .await + .unwrap(); + +println!( + "View transaction on MidenScan: https://testnet.midenscan.com/tx/{:?}", + tx_id +); + +client.sync_state().await.unwrap(); + +sleep(Duration::from_secs(5)).await; + +client.sync_state().await.unwrap(); + +// Retrieve updated contract data to see the incremented counter +let account_1 = client.get_account(counter_contract.id()).await.unwrap(); +println!( + "counter contract storage: {:?}", + account_1.unwrap().account().storage().get_item(0) +); + +let account_2 = client + .get_account(count_reader_contract.id()) + .await + .unwrap(); +println!( + "count reader contract storage: {:?}", + account_2.unwrap().account().storage().get_item(0) +); +``` + +The key here is the use of the `.foreign_accounts()` method on the `TransactionRequestBuilder`. Using this method, it is possible to create transactions with multiple foreign procedure calls. + +## Summary + +In this tutorial created a smart contract that calls the `get_count` procedure in the counter contract using foreign procedure invocation, and then saves the returned value to its local storage. + +The final `src/main.rs` file should look like this: + +```rust +use miden_crypto::Felt; +use miden_lib::account::auth::NoAuth; +use miden_lib::transaction::TransactionKernel; +use rand::{rngs::StdRng, RngCore}; +use std::{fs, path::Path, sync::Arc, time::Duration}; +use tokio::time::sleep; + +use miden_client::{ + assembly::{Assembler, DefaultSourceManager, LibraryPath, Module, ModuleKind}, + builder::ClientBuilder, + keystore::FilesystemKeyStore, + rpc::{domain::account::AccountStorageRequirements, Endpoint, GrpcClient}, + transaction::{ForeignAccount, TransactionRequestBuilder}, + ClientError, +}; +use miden_client_sqlite_store::ClientBuilderSqliteExt; +use miden_objects::{ + account::{ + AccountBuilder, AccountComponent, AccountId, AccountStorageMode, AccountType, StorageSlot, + }, + assembly::mast::MastNodeExt, + Word, +}; + +fn create_library( + assembler: Assembler, + library_path: &str, + source_code: &str, +) -> Result> { + let source_manager = Arc::new(DefaultSourceManager::default()); + let module = Module::parser(ModuleKind::Library).parse_str( + LibraryPath::new(library_path)?, + source_code, + &source_manager, + )?; + let library = assembler.clone().assemble_library([module])?; + Ok(library) +} + +#[tokio::main] +async fn main() -> Result<(), ClientError> { + // Initialize client + let endpoint = Endpoint::testnet(); + let timeout_ms = 10_000; + let rpc_client = Arc::new(GrpcClient::new(&endpoint, timeout_ms)); + + // Initialize keystore + let keystore_path = std::path::PathBuf::from("./keystore"); + let keystore = Arc::new(FilesystemKeyStore::::new(keystore_path).unwrap()); + + let store_path = std::path::PathBuf::from("./store.sqlite3"); + + let mut client = ClientBuilder::new() + .rpc(rpc_client) + .sqlite_store(store_path) + .authenticator(keystore.clone()) + .in_debug_mode(true.into()) + .build() + .await?; + + let sync_summary = client.sync_state().await.unwrap(); + println!("Latest block: {}", sync_summary.block_num); + + // ------------------------------------------------------------------------- + // STEP 1: Create the Count Reader Contract + // ------------------------------------------------------------------------- + println!("\n[STEP 1] Creating count reader contract."); + + // Load the MASM file for the counter contract + let count_reader_path = Path::new("../masm/accounts/count_reader.masm"); + let count_reader_code = fs::read_to_string(count_reader_path).unwrap(); + + // Prepare assembler (debug mode = true) + let assembler = TransactionKernel::assembler().with_debug_mode(true); + + // Compile the account code into `AccountComponent` with one storage slot + let count_reader_component = AccountComponent::compile( + &count_reader_code, + TransactionKernel::assembler(), + vec![StorageSlot::Value(Word::default())], + ) + .unwrap() + .with_supports_all_types(); + + // Init seed for the counter contract + let mut init_seed = [0_u8; 32]; + client.rng().fill_bytes(&mut init_seed); + + // Build the new `Account` with the component + let count_reader_contract = AccountBuilder::new(init_seed) + .account_type(AccountType::RegularAccountImmutableCode) + .storage_mode(AccountStorageMode::Public) + .with_component(count_reader_component.clone()) + .with_auth_component(NoAuth) + .build() + .unwrap(); + + println!( + "count_reader hash: {:?}", + count_reader_contract.commitment() + ); + println!("contract id: {:?}", count_reader_contract.id()); + + client + .add_account(&count_reader_contract, false) + .await + .unwrap(); + + // ------------------------------------------------------------------------- + // STEP 2: Build & Get State of the Counter Contract + // ------------------------------------------------------------------------- + println!("\n[STEP 2] Building counter contract from public state"); + + // Define the Counter Contract account id from counter contract deploy + let (_, counter_contract_id) = + AccountId::from_bech32("mtst1arjemrxne8lj5qz4mg9c8mtyxg954483").unwrap(); + + println!("counter contract id: {:?}", counter_contract_id); + + client + .import_account_by_id(counter_contract_id) + .await + .unwrap(); + + let counter_contract_details = client.get_account(counter_contract_id).await.unwrap(); + + let counter_contract = if let Some(account_record) = counter_contract_details { + // Clone the account to get an owned instance + let account = account_record.account().clone(); + println!( + "Account details: {:?}", + account.storage().slots().first().unwrap() + ); + account // Now returns an owned account + } else { + panic!("Counter contract not found!"); + }; + + // ------------------------------------------------------------------------- + // STEP 3: Call the Counter Contract via Foreign Procedure Invocation (FPI) + // ------------------------------------------------------------------------- + println!("\n[STEP 3] Call counter contract with FPI from count copy contract"); + + let counter_contract_path = Path::new("../masm/accounts/counter.masm"); + let counter_contract_code = fs::read_to_string(counter_contract_path).unwrap(); + + let counter_contract_component = AccountComponent::compile( + &counter_contract_code, + TransactionKernel::assembler(), + vec![], + ) + .unwrap() + .with_supports_all_types(); + + // Getting the hash of the `get_count` procedure + let get_proc_export = counter_contract_component + .library() + .exports() + .find(|export| export.name.name.as_str() == "get_count") + .unwrap(); + + let get_proc_mast_id = counter_contract_component + .library() + .get_export_node_id(&get_proc_export.name); + + let get_count_hash = counter_contract_component + .library() + .mast_forest() + .get_node_by_id(get_proc_mast_id) + .unwrap() + .digest() + .as_elements() + .iter() + .map(|f: &Felt| format!("{}", f.as_int())) + .collect::>() + .join("."); + + println!("get count hash: {:?}", get_count_hash); + println!("counter id prefix: {:?}", counter_contract.id().prefix()); + println!("suffix: {:?}", counter_contract.id().suffix()); + + // Build the script that calls the count_copy_contract + let script_path = Path::new("../masm/scripts/reader_script.masm"); + let script_code_original = fs::read_to_string(script_path).unwrap(); + let script_code = script_code_original + .replace("{get_count_proc_hash}", &get_count_hash) + .replace( + "{account_id_suffix}", + &counter_contract.id().suffix().to_string(), + ) + .replace( + "{account_id_prefix}", + &counter_contract.id().prefix().to_string(), + ); + + let account_component_lib = create_library( + assembler.clone(), + "external_contract::count_reader_contract", + &count_reader_code, + ) + .unwrap(); + + let tx_script = client + .script_builder() + .with_dynamically_linked_library(&account_component_lib) + .unwrap() + .compile_tx_script(&script_code) + .unwrap(); + + let foreign_account = + ForeignAccount::public(counter_contract_id, AccountStorageRequirements::default()).unwrap(); + + // Build a transaction request with the custom script + let tx_request = TransactionRequestBuilder::new() + .foreign_accounts([foreign_account]) + .custom_script(tx_script) + .build() + .unwrap(); + + // Execute and submit the transaction + let tx_id = client + .submit_new_transaction(count_reader_contract.id(), tx_request) + .await + .unwrap(); + + println!( + "View transaction on MidenScan: https://testnet.midenscan.com/tx/{:?}", + tx_id + ); + + client.sync_state().await.unwrap(); + + sleep(Duration::from_secs(5)).await; + + client.sync_state().await.unwrap(); + + // Retrieve updated contract data to see the incremented counter + let account_1 = client.get_account(counter_contract.id()).await.unwrap(); + println!( + "counter contract storage: {:?}", + account_1.unwrap().account().storage().get_item(0) + ); + + let account_2 = client + .get_account(count_reader_contract.id()) + .await + .unwrap(); + println!( + "count reader contract storage: {:?}", + account_2.unwrap().account().storage().get_item(0) + ); + + Ok(()) +} +``` + +The output of our program will look something like this: + +```text +Latest block: 17916 + +[STEP 1] Creating count reader contract. +count_reader hash: RpoDigest([17106452548071357259, 1177663122773866223, 12129142941281960455, 8269441041947541276]) +contract id: "0x4e79c8d2334239000000197081e311" + +[STEP 2] Building counter contract from public state +Account details: Value([0, 0, 0, 2]) + +[STEP 3] Call Counter Contract with FPI from Count Copy Contract +get count hash: "0x92495ca54d519eb5e4ba22350f837904d3895e48d74d8079450f19574bb84cb6" +counter id prefix: V0(AccountIdPrefixV0 { prefix: 1170938688336660224 }) +suffix: 204650730194688 +Stack state before step 2248: +├── 0: 111 +├── 1: 1170938688336660224 +├── 2: 204650730194688 +├── 3: 13136076846856212293 +├── 4: 8755083262635706835 +├── 5: 322432949672917732 +├── 6: 13086986961113860498 +├── 7: 0 +├── 8: 0 +├── 9: 0 +├── 10: 0 +├── 11: 0 +├── 12: 0 +├── 13: 0 +├── 14: 0 +├── 15: 0 +├── 16: 0 +├── 17: 0 +├── 18: 0 +├── 19: 0 +├── 20: 0 +├── 21: 0 +└── 22: 0 + +Stack state before step 3224: +├── 0: 2 +├── 1: 0 +├── 2: 0 +├── 3: 0 +├── 4: 0 +├── 5: 0 +├── 6: 0 +├── 7: 0 +├── 8: 0 +├── 9: 0 +├── 10: 0 +├── 11: 0 +├── 12: 0 +├── 13: 0 +├── 14: 0 +├── 15: 0 +├── 16: 0 +├── 17: 0 +├── 18: 0 +├── 19: 0 +├── 20: 0 +└── 21: 0 + +View transaction on MidenScan: https://testnet.midenscan.com/tx/0x7144cf2648a7001a9972aed73596db070a679b467fec83263846a5a4f8eb74e6 +counter contract storage: Ok(RpoDigest([0, 0, 0, 2])) +count reader contract storage: Ok(RpoDigest([0, 0, 0, 2])) +``` + +### Running the example + +To run the full example, navigate to the `rust-client` directory in the [miden-tutorials](https://github.com/0xMiden/miden-tutorials/) repository and run this command: + +```bash +cd rust-client +cargo run --release --bin counter_contract_fpi +``` + +### Continue learning + +Next tutorial: [How to Use Unauthenticated Notes](unauthenticated_note_how_to.md) diff --git a/versioned_docs/version-0.12 (stable)/miden-tutorials/rust-client/index.md b/versioned_docs/version-0.12 (stable)/miden-tutorials/rust-client/index.md new file mode 100644 index 0000000..4515a95 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-tutorials/rust-client/index.md @@ -0,0 +1,18 @@ +--- +title: "Rust Client" +sidebar_position: 1 +--- + +# Rust Client + +Rust library, which can be used to programmatically interact with the Miden rollup. + +The Miden Rust client can be used for a variety of things, including: + +- Deploying, testing, and creating transactions to interact with accounts and notes on Miden. +- Storing the state of accounts and notes locally. +- Generating and submitting proofs of transactions. + +This section of the docs is an overview of the different things one can achieve using the Rust client, and how to implement them. + +Keep in mind that both the Rust client and the documentation are works-in-progress! diff --git a/versioned_docs/version-0.12 (stable)/miden-tutorials/rust-client/mappings_in_masm_how_to.md b/versioned_docs/version-0.12 (stable)/miden-tutorials/rust-client/mappings_in_masm_how_to.md new file mode 100644 index 0000000..3e1da1c --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-tutorials/rust-client/mappings_in_masm_how_to.md @@ -0,0 +1,345 @@ +--- +title: "How to Use Mappings in Miden Assembly" +sidebar_position: 10 +--- + +# How to Use Mappings in Miden Assembly + +_Using mappings in Miden assembly for storing key value pairs_ + +## Overview + +In this example, we will explore how to use mappings in Miden Assembly. Mappings are essential data structures that store key-value pairs. We will demonstrate how to create an account that contains a mapping and then call a procedure in that account to update the mapping. + +At a high level, this example involves: + +- Setting up an account with a mapping stored in one of its storage slots. +- Writing a smart contract in Miden Assembly that includes procedures to read from and write to the mapping. +- Creating a transaction script that calls these procedures. +- Using Rust code to deploy the account and submit a transaction that updates the mapping. + After the Miden Assembly snippets, we explain that the transaction script calls a procedure in the account. This procedure then updates the mapping by modifying the mapping stored in the account's storage slot. + +## What we'll cover + +- **How to Use Mappings in Miden Assembly:** See how to create a smart contract that uses a mapping. +- **How to Link Libraries in Miden Assembly:** Demonstrate how to link procedures across Accounts, Notes, and Scripts. + +## Step-by-step process + +1. **Setting up an account with a mapping** + In this step, you create an account that has a storage slot configured as a mapping. The account smart contract code (shown below) defines procedures to write to and read from this mapping. + +2. **Creating a script that calls a procedure in the account:** + Next, you create a transaction script that calls the procedures defined in the account. This script sends the key-value data and then invokes the account procedure, which updates the mapping. + +3. **How to read and write to a mapping in MASM:** + Finally, we demonstrate how to use MASM instructions to interact with the mapping. The smart contract uses standard procedures to set a mapping item, retrieve a value from the mapping, and get the current mapping root. + +--- + +### Example of smart contract that uses a mapping + +```masm +use.miden::active_account +use.miden::native_account +use.std::sys + +# Inputs: [KEY, VALUE] +# Outputs: [] +export.write_to_map + # The storage map is in storage slot 1 + push.1 + # => [index, KEY, VALUE] + + # Setting the key value pair in the map + exec.native_account::set_map_item + # => [OLD_MAP_ROOT, OLD_MAP_VALUE] + + dropw dropw dropw dropw + # => [] +end + +# Inputs: [KEY] +# Outputs: [VALUE] +export.get_value_in_map + # The storage map is in storage slot 1 + push.1 + # => [index] + + exec.active_account::get_map_item + # => [VALUE] +end + +# Inputs: [] +# Outputs: [CURRENT_ROOT] +export.get_current_map_root + # Getting the current root from slot 1 + push.1 exec.active_account::get_item + # => [CURRENT_ROOT] + + exec.sys::truncate_stack + # => [CURRENT_ROOT] +end +``` + +### Explanation of the assembly code + +- **write_to_map:** + The procedure takes a key and a value as inputs. It pushes the storage index (`0` for our mapping) onto the stack, then calls the `set_map_item` procedure from the account library to update the mapping. After updating the map, it drops any unused outputs and increments the nonce. +- **get_value_in_map:** + This procedure takes a key as input and retrieves the corresponding value from the mapping by calling `get_map_item` after pushing the mapping index. + +- **get_current_map_root:** + This procedure retrieves the current root of the mapping (stored at index `0`) by calling `get_item` and then truncating the stack to leave only the mapping root. + +**Security Note**: The procedure `write_to_map` calls the account procedure `incr_nonce`. This allows any external account to be able to write to the storage map of the account. Smart contract developers should know that procedures that call the `account::incr_nonce` procedure allow anyone to call the procedure and modify the state of the account. + +### Transaction script that calls the smart contract + +```masm +use.miden_by_example::mapping_example_contract +use.std::sys + +begin + push.1.2.3.4 + push.0.0.0.0 + # => [KEY, VALUE] + + call.mapping_example_contract::write_to_map + # => [] + + push.0.0.0.0 + # => [KEY] + + call.mapping_example_contract::get_value_in_map + # => [VALUE] + + dropw + # => [] + + call.mapping_example_contract::get_current_map_root + # => [CURRENT_ROOT] + + exec.sys::truncate_stack +end +``` + +### Explanation of the transaction script + +The transaction script does the following: + +- It pushes a key (`[0.0.0.0]`) and a value (`[1.2.3.4]`) onto the stack. +- It calls the `write_to_map` procedure, which is defined in the account’s smart contract. This updates the mapping in the account. +- It then pushes the key again and calls `get_value_in_map` to retrieve the value associated with the key. +- Finally, it calls `get_current_map_root` to get the current state (root) of the mapping. + +The script calls the `write_to_map` procedure in the account which writes the key value pair to the mapping. + +--- + +### Rust code that sets everything up + +Below is the Rust code that deploys the smart contract, creates the transaction script, and submits a transaction to update the mapping in the account: + +```rust +use miden_lib::account::auth::NoAuth; +use miden_lib::transaction::TransactionKernel; +use rand::{rngs::StdRng, RngCore}; +use std::{fs, path::Path, sync::Arc}; + +use miden_client::{ + assembly::{Assembler, DefaultSourceManager, LibraryPath, Module, ModuleKind}, + builder::ClientBuilder, + keystore::FilesystemKeyStore, + rpc::{Endpoint, GrpcClient}, + transaction::TransactionRequestBuilder, + ClientError, +}; +use miden_client_sqlite_store::ClientBuilderSqliteExt; +use miden_objects::{ + account::{ + AccountBuilder, AccountComponent, AccountStorageMode, AccountType, StorageMap, StorageSlot, + }, + Felt, Word, +}; + +fn create_library( + assembler: Assembler, + library_path: &str, + source_code: &str, +) -> Result> { + let source_manager = Arc::new(DefaultSourceManager::default()); + let module = Module::parser(ModuleKind::Library).parse_str( + LibraryPath::new(library_path)?, + source_code, + &source_manager, + )?; + let library = assembler.clone().assemble_library([module])?; + Ok(library) +} + +#[tokio::main] +async fn main() -> Result<(), ClientError> { + // Initialize client + let endpoint = Endpoint::testnet(); + let timeout_ms = 10_000; + let rpc_client = Arc::new(GrpcClient::new(&endpoint, timeout_ms)); + + // Initialize keystore + let keystore_path = std::path::PathBuf::from("./keystore"); + let keystore = Arc::new(FilesystemKeyStore::::new(keystore_path).unwrap()); + + let store_path = std::path::PathBuf::from("./store.sqlite3"); + + let mut client = ClientBuilder::new() + .rpc(rpc_client) + .sqlite_store(store_path) + .authenticator(keystore.clone()) + .in_debug_mode(true.into()) + .build() + .await?; + + let sync_summary = client.sync_state().await.unwrap(); + println!("Latest block: {}", sync_summary.block_num); + + // ------------------------------------------------------------------------- + // STEP 1: Deploy a smart contract with a mapping + // ------------------------------------------------------------------------- + println!("\n[STEP 1] Deploy a smart contract with a mapping"); + + // Load the MASM file for the counter contract + let file_path = Path::new("../masm/accounts/mapping_example_contract.masm"); + let account_code = fs::read_to_string(file_path).unwrap(); + + // Prepare assembler (debug mode = true) + let assembler: Assembler = TransactionKernel::assembler().with_debug_mode(true); + + // Using an empty storage value in slot 0 since this is usually reserved + // for the account pub_key and metadata + let empty_storage_slot = StorageSlot::Value(Word::default()); + + // initialize storage map + let storage_map = StorageMap::new(); + let storage_slot_map = StorageSlot::Map(storage_map.clone()); + + // Compile the account code into `AccountComponent` with one storage slot + let mapping_contract_component = AccountComponent::compile( + &account_code, + assembler.clone(), + vec![empty_storage_slot, storage_slot_map], + ) + .unwrap() + .with_supports_all_types(); + + // Init seed for the counter contract + let mut init_seed = [0_u8; 32]; + client.rng().fill_bytes(&mut init_seed); + + // Build the new `Account` with the component + let mapping_example_contract = AccountBuilder::new(init_seed) + .account_type(AccountType::RegularAccountImmutableCode) + .storage_mode(AccountStorageMode::Public) + .with_component(mapping_contract_component.clone()) + .with_auth_component(NoAuth) + .build() + .unwrap(); + + client + .add_account(&mapping_example_contract, false) + .await + .unwrap(); + + // ------------------------------------------------------------------------- + // STEP 2: Call the Mapping Contract with a Script + // ------------------------------------------------------------------------- + println!("\n[STEP 2] Call Mapping Contract With Script"); + + let script_code = + fs::read_to_string(Path::new("../masm/scripts/mapping_example_script.masm")).unwrap(); + + // Create the library from the account source code using the helper function. + let account_component_lib = create_library( + assembler.clone(), + "miden_by_example::mapping_example_contract", + &account_code, + ) + .unwrap(); + + // Compile the transaction script with the library. + let tx_script = client + .script_builder() + .with_dynamically_linked_library(&account_component_lib) + .unwrap() + .compile_tx_script(&script_code) + .unwrap(); + + // Build a transaction request with the custom script + let tx_increment_request = TransactionRequestBuilder::new() + .custom_script(tx_script) + .build() + .unwrap(); + + // Execute and submit the transaction + let tx_id = client + .submit_new_transaction(mapping_example_contract.id(), tx_increment_request) + .await + .unwrap(); + + println!( + "View transaction on MidenScan: https://testnet.midenscan.com/tx/{:?}", + tx_id + ); + + client.sync_state().await.unwrap(); + + let account = client + .get_account(mapping_example_contract.id()) + .await + .unwrap(); + let index = 1; + let key = [Felt::new(0), Felt::new(0), Felt::new(0), Felt::new(0)].into(); + println!( + "Mapping state\n Index: {:?}\n Key: {:?}\n Value: {:?}", + index, + key, + account + .unwrap() + .account() + .storage() + .get_map_item(index, key) + ); + + Ok(()) +} +``` + +### What the Rust code does + +- **Client Initialization:** + The client is initialized with a connection to the Miden Testnet and a SQLite store. This sets up the environment to deploy and interact with accounts. + +- **Deploying the Smart Contract:** + The account containing the mapping is created by reading the MASM smart contract from a file, compiling it into an `AccountComponent`, and deploying it using an `AccountBuilder`. + +- **Creating and Executing a Transaction Script:** + A separate MASM script is compiled into a `TransactionScript`. This script calls the smart contract's procedures to write to and then read from the mapping. + +- **Displaying the Result:** + Finally, after the transaction is processed, the code reads the updated state of the mapping in the account. + +--- + +### Running the example + +To run the full example, navigate to the `rust-client` directory in the [miden-tutorials](https://github.com/0xMiden/miden-tutorials/) repository and run this command: + +```bash +cd rust-client +cargo run --release --bin mapping_example +``` + +This example shows how the script calls the procedure in the account, which then updates the mapping stored within the account. The mapping update is verified by reading the mapping’s key-value pair after the transaction completes. + +### Continue learning + +Next tutorial: [How to Create Notes in Miden Assembly](creating_notes_in_masm_tutorial.md) diff --git a/versioned_docs/version-0.12 (stable)/miden-tutorials/rust-client/mint_consume_create_tutorial.md b/versioned_docs/version-0.12 (stable)/miden-tutorials/rust-client/mint_consume_create_tutorial.md new file mode 100644 index 0000000..c5a79a9 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-tutorials/rust-client/mint_consume_create_tutorial.md @@ -0,0 +1,592 @@ +--- +title: "Mint, Consume, and Create Notes" +sidebar_position: 3 +--- + +# Mint, Consume, and Create Notes + +_Using the Miden client in Rust to mint, consume, and create notes_ + +## Overview + +In the previous section, we initialized our repository and covered how to create an account and deploy a faucet. In this section, we will mint tokens from the faucet for _Alice_, consume the newly created notes, and demonstrate how to send assets to other accounts. + +## What we'll cover + +- Minting tokens from a faucet +- Consuming notes to fund an account +- Sending tokens to other users + +## Step 1: Minting tokens from the faucet + +To mint notes with tokens from the faucet we created, Alice needs to call the faucet with a mint transaction request. + +_In essence, a transaction request is a structured template that outlines the data required to generate a zero-knowledge proof of a state change of an account. It specifies which input notes (if any) will be consumed, includes an optional transaction script to execute, and enumerates the set of notes expected to be created (if any)._ + +Below is an example of a transaction request minting tokens from the faucet for Alice. This code snippet will create 5 transaction mint transaction requests. + +Add this snippet to the end of your file in the `main()` function that we created in the previous chapter: + +```rust ignore +//------------------------------------------------------------ +// STEP 3: Mint 5 notes of 100 tokens for Alice +//------------------------------------------------------------ +println!("\n[STEP 3] Minting 5 notes of 100 tokens each for Alice."); + +let amount: u64 = 100; +let fungible_asset = FungibleAsset::new(faucet_account.id(), amount).unwrap(); + +for i in 1..=5 { + let transaction_request = TransactionRequestBuilder::new() + .build_mint_fungible_asset( + fungible_asset, + alice_account.id(), + NoteType::Public, + client.rng(), + ) + .unwrap(); + + println!("tx request built"); + + let tx_id = client + .submit_new_transaction(faucet_account.id(), transaction_request) + .await?; + println!( + "Minted note #{} of {} tokens for Alice. TX: {:?}", + i, amount, tx_id + ); +} +println!("All 5 notes minted for Alice successfully!"); + +// Re-sync so minted notes become visible +client.sync_state().await?; +``` + +## Step 2: Identifying consumable notes + +Once Alice has minted a note from the faucet, she will eventually want to spend the tokens that she received in the note created by the mint transaction. + +Minting a note from a faucet on Miden means a faucet account creates a new note targeted to the requesting account. The requesting account needs to consume this new note to have the assets appear in their account. + +To identify consumable notes, the Miden client provides the `get_consumable_notes` function. Before calling it, ensure that the client state is synced. + +_Tip: If you know how many notes to expect after a transaction, use an await or loop condition to check how many notes of the type you expect are available for consumption instead of using a set timeout before calling `get_consumable_notes`. This ensures your application isn't idle for longer than necessary._ + +#### Identifying which notes are available: + +```rust ignore +let consumable_notes = client.get_consumable_notes(Some(alice_account.id())).await?; +``` + +## Step 3: Consuming multiple notes in a single transaction: + +Now that we know how to identify notes ready to consume, let's consume the notes created by the faucet in a single transaction. After consuming the notes, Alice's wallet balance will be updated. + +The following code snippet identifies consumable notes and consumes them in a single transaction. + +Add this snippet to the end of your file in the `main()` function: + +```rust ignore +//------------------------------------------------------------ +// STEP 4: Alice consumes all her notes +//------------------------------------------------------------ +println!("\n[STEP 4] Alice will now consume all of her notes to consolidate them."); + +// Consume all minted notes in a single transaction +loop { + // Resync to get the latest data + client.sync_state().await?; + + let consumable_notes = client + .get_consumable_notes(Some(alice_account.id())) + .await?; + let list_of_note_ids: Vec<_> = consumable_notes.iter().map(|(note, _)| note.id()).collect(); + + if list_of_note_ids.len() == 5 { + println!("Found 5 consumable notes for Alice. Consuming them now..."); + let transaction_request = TransactionRequestBuilder::new() + .build_consume_notes(list_of_note_ids) + .unwrap(); + + let tx_id = client + .submit_new_transaction(alice_account.id(), transaction_request) + .await?; + println!( + "All of Alice's notes consumed successfully. TX: {:?}", + tx_id + ); + break; + } else { + println!( + "Currently, Alice has {} consumable notes. Waiting...", + list_of_note_ids.len() + ); + tokio::time::sleep(Duration::from_secs(3)).await; + } +} +``` + +## Step 4: Sending tokens to other accounts + +After consuming the notes, Alice has tokens in her wallet. Now, she wants to send tokens to her friends. She has two options: create a separate transaction for each transfer or batch multiple transfers into a single transaction. + +_The standard asset transfer note on Miden is the P2ID note (Pay-to-Id). There is also the P2IDE (Pay-to-Id Extended) variant which allows for both timelocking the note (target can only spend the note after a certain block height) and for the note to be reclaimable (the creator of the note can reclaim the note after a certain block height)._ + +In our example, Alice will now send 50 tokens to 5 different accounts. + +For the sake of the example, the first four P2ID transfers are handled in a single transaction, and the fifth transfer is a standard P2ID transfer. + +### Output multiple P2ID notes in a single transaction + +To output multiple notes in a single transaction we need to create a list of our expected output notes. The expected output notes are the notes that we expect to create in our transaction request. + +In the snippet below, we create an empty vector to store five P2ID output notes, loop over five iterations `(using 0..=4)` to create five unique dummy account IDs, build a P2ID note for each one, and push each note onto the vector. Finally, we build a transaction request using `.own_output_notes()`—passing in all five notes—and submit it to the node. + +Add this snippet to the end of your file in the `main()` function: + +```rust ignore +//------------------------------------------------------------ +// STEP 5: Alice sends 5 notes of 50 tokens to 5 users +//------------------------------------------------------------ +println!("\n[STEP 5] Alice sends 5 notes of 50 tokens each to 5 different users."); + +// Send 50 tokens to 4 accounts in one transaction +println!("Creating multiple P2ID notes for 4 target accounts in one transaction..."); +let mut p2id_notes = vec![]; + +// Creating 4 P2ID notes to 4 'dummy' AccountIds +for _ in 1..=4 { + let init_seed: [u8; 15] = { + let mut init_seed = [0_u8; 15]; + client.rng().fill_bytes(&mut init_seed); + init_seed + }; + let target_account_id = AccountId::dummy( + init_seed, + AccountIdVersion::Version0, + AccountType::RegularAccountUpdatableCode, + AccountStorageMode::Public, + ); + + let send_amount = 50; + let fungible_asset = FungibleAsset::new(faucet_account.id(), send_amount).unwrap(); + + let p2id_note = create_p2id_note( + alice_account.id(), + target_account_id, + vec![fungible_asset.into()], + NoteType::Public, + Felt::new(0), + client.rng(), + )?; + p2id_notes.push(p2id_note); +} + +// Specifying output notes and creating a tx request to create them +let output_notes: Vec = p2id_notes.into_iter().map(OutputNote::Full).collect(); +let transaction_request = TransactionRequestBuilder::new() + .own_output_notes(output_notes) + .build() + .unwrap(); + +let tx_id = client + .submit_new_transaction(alice_account.id(), transaction_request) + .await?; + +println!("Submitted a transaction with 4 P2ID notes. TX: {:?}", tx_id); +``` + +### Basic P2ID transfer + +Now as an example, Alice will send some tokens to an account in a single transaction. + +Add this snippet to the end of your file in the `main()` function: + +```rust ignore +println!("Submitting one more single P2ID transaction..."); +let init_seed: [u8; 15] = { + let mut init_seed = [0_u8; 15]; + client.rng().fill_bytes(&mut init_seed); + init_seed +}; +let target_account_id = AccountId::dummy( + init_seed, + AccountIdVersion::Version0, + AccountType::RegularAccountUpdatableCode, + AccountStorageMode::Public, +); + +let send_amount = 50; +let fungible_asset = FungibleAsset::new(faucet_account.id(), send_amount).unwrap(); + +let p2id_note = create_p2id_note( + alice_account.id(), + target_account_id, + vec![fungible_asset.into()], + NoteType::Public, + Felt::new(0), + client.rng(), +)?; + +let transaction_request = TransactionRequestBuilder::new() + .own_output_notes(vec![OutputNote::Full(p2id_note)]) + .build() + .unwrap(); + +let tx_id = client + .submit_new_transaction(alice_account.id(), transaction_request) + .await?; + +println!("Submitted final P2ID transaction. TX: {:?}", tx_id); +``` + +Note: _In a production environment do not use `AccountId::new_dummy()`, this is simply for the sake of the tutorial example._ + +## Summary + +Your `src/main.rs` function should now look like this: + +```rust +use miden_lib::account::auth::AuthRpoFalcon512; +use rand::{rngs::StdRng, RngCore}; +use std::sync::Arc; +use tokio::time::Duration; + +use miden_client::{ + account::{ + component::{BasicFungibleFaucet, BasicWallet}, + AccountId, + }, + address::NetworkId, + auth::AuthSecretKey, + builder::ClientBuilder, + keystore::FilesystemKeyStore, + note::{create_p2id_note, NoteType}, + rpc::{Endpoint, GrpcClient}, + transaction::{OutputNote, TransactionRequestBuilder}, + ClientError, +}; +use miden_client_sqlite_store::ClientBuilderSqliteExt; +use miden_objects::{ + account::{AccountBuilder, AccountIdVersion, AccountStorageMode, AccountType}, + asset::{FungibleAsset, TokenSymbol}, + Felt, +}; + +#[tokio::main] +async fn main() -> Result<(), ClientError> { + // Initialize client + let endpoint = Endpoint::testnet(); + let timeout_ms = 10_000; + let rpc_client = Arc::new(GrpcClient::new(&endpoint, timeout_ms)); + + // Initialize keystore + let keystore_path = std::path::PathBuf::from("./keystore"); + let keystore = Arc::new(FilesystemKeyStore::::new(keystore_path).unwrap()); + + let store_path = std::path::PathBuf::from("./store.sqlite3"); + + let mut client = ClientBuilder::new() + .rpc(rpc_client) + .sqlite_store(store_path) + .authenticator(keystore.clone()) + .in_debug_mode(true.into()) + .build() + .await?; + + let sync_summary = client.sync_state().await.unwrap(); + println!("Latest block: {}", sync_summary.block_num); + + //------------------------------------------------------------ + // STEP 1: Create a basic wallet for Alice + //------------------------------------------------------------ + println!("\n[STEP 1] Creating a new account for Alice"); + + // Account seed + let mut init_seed = [0_u8; 32]; + client.rng().fill_bytes(&mut init_seed); + + let key_pair = AuthSecretKey::new_rpo_falcon512(); + + // Build the account + let alice_account = AccountBuilder::new(init_seed) + .account_type(AccountType::RegularAccountUpdatableCode) + .storage_mode(AccountStorageMode::Public) + .with_auth_component(AuthRpoFalcon512::new(key_pair.public_key().to_commitment())) + .with_component(BasicWallet) + .build() + .unwrap(); + + // Add the account to the client + client.add_account(&alice_account, false).await?; + + // Add the key pair to the keystore + keystore.add_key(&key_pair).unwrap(); + + let alice_account_id_bech32 = alice_account.id().to_bech32(NetworkId::Testnet); + println!("Alice's account ID: {:?}", alice_account_id_bech32); + + //------------------------------------------------------------ + // STEP 2: Deploy a fungible faucet + //------------------------------------------------------------ + println!("\n[STEP 2] Deploying a new fungible faucet."); + + // Faucet seed + let mut init_seed = [0u8; 32]; + client.rng().fill_bytes(&mut init_seed); + + // Faucet parameters + let symbol = TokenSymbol::new("MID").unwrap(); + let decimals = 8; + let max_supply = Felt::new(1_000_000); + + // Generate key pair + let key_pair = AuthSecretKey::new_rpo_falcon512(); + + // Build the faucet account + let faucet_account = AccountBuilder::new(init_seed) + .account_type(AccountType::FungibleFaucet) + .storage_mode(AccountStorageMode::Public) + .with_auth_component(AuthRpoFalcon512::new(key_pair.public_key().to_commitment())) + .with_component(BasicFungibleFaucet::new(symbol, decimals, max_supply).unwrap()) + .build() + .unwrap(); + + // Add the faucet to the client + client.add_account(&faucet_account, false).await?; + + // Add the key pair to the keystore + keystore.add_key(&key_pair).unwrap(); + + let faucet_account_id_bech32 = faucet_account.id().to_bech32(NetworkId::Testnet); + println!("Faucet account ID: {:?}", faucet_account_id_bech32); + + // Resync to show newly deployed faucet + client.sync_state().await?; + tokio::time::sleep(Duration::from_secs(2)).await; + + //------------------------------------------------------------ + // STEP 3: Mint 5 notes of 100 tokens for Alice + //------------------------------------------------------------ + println!("\n[STEP 3] Minting 5 notes of 100 tokens each for Alice."); + + let amount: u64 = 100; + let fungible_asset = FungibleAsset::new(faucet_account.id(), amount).unwrap(); + + for i in 1..=5 { + let transaction_request = TransactionRequestBuilder::new() + .build_mint_fungible_asset( + fungible_asset, + alice_account.id(), + NoteType::Public, + client.rng(), + ) + .unwrap(); + + println!("tx request built"); + + let tx_id = client + .submit_new_transaction(faucet_account.id(), transaction_request) + .await?; + println!( + "Minted note #{} of {} tokens for Alice. TX: {:?}", + i, amount, tx_id + ); + } + println!("All 5 notes minted for Alice successfully!"); + + // Re-sync so minted notes become visible + client.sync_state().await?; + + //------------------------------------------------------------ + // STEP 4: Alice consumes all her notes + //------------------------------------------------------------ + println!("\n[STEP 4] Alice will now consume all of her notes to consolidate them."); + + // Consume all minted notes in a single transaction + loop { + // Resync to get the latest data + client.sync_state().await?; + + let consumable_notes = client + .get_consumable_notes(Some(alice_account.id())) + .await?; + let list_of_note_ids: Vec<_> = consumable_notes.iter().map(|(note, _)| note.id()).collect(); + + if list_of_note_ids.len() == 5 { + println!("Found 5 consumable notes for Alice. Consuming them now..."); + let transaction_request = TransactionRequestBuilder::new() + .build_consume_notes(list_of_note_ids) + .unwrap(); + + let tx_id = client + .submit_new_transaction(alice_account.id(), transaction_request) + .await?; + println!( + "All of Alice's notes consumed successfully. TX: {:?}", + tx_id + ); + break; + } else { + println!( + "Currently, Alice has {} consumable notes. Waiting...", + list_of_note_ids.len() + ); + tokio::time::sleep(Duration::from_secs(3)).await; + } + } + + //------------------------------------------------------------ + // STEP 5: Alice sends 5 notes of 50 tokens to 5 users + //------------------------------------------------------------ + println!("\n[STEP 5] Alice sends 5 notes of 50 tokens each to 5 different users."); + + // Send 50 tokens to 4 accounts in one transaction + println!("Creating multiple P2ID notes for 4 target accounts in one transaction..."); + let mut p2id_notes = vec![]; + + // Creating 4 P2ID notes to 4 'dummy' AccountIds + for _ in 1..=4 { + let init_seed: [u8; 15] = { + let mut init_seed = [0_u8; 15]; + client.rng().fill_bytes(&mut init_seed); + init_seed + }; + let target_account_id = AccountId::dummy( + init_seed, + AccountIdVersion::Version0, + AccountType::RegularAccountUpdatableCode, + AccountStorageMode::Public, + ); + + let send_amount = 50; + let fungible_asset = FungibleAsset::new(faucet_account.id(), send_amount).unwrap(); + + let p2id_note = create_p2id_note( + alice_account.id(), + target_account_id, + vec![fungible_asset.into()], + NoteType::Public, + Felt::new(0), + client.rng(), + )?; + p2id_notes.push(p2id_note); + } + + // Specifying output notes and creating a tx request to create them + let output_notes: Vec = p2id_notes.into_iter().map(OutputNote::Full).collect(); + let transaction_request = TransactionRequestBuilder::new() + .own_output_notes(output_notes) + .build() + .unwrap(); + + let tx_id = client + .submit_new_transaction(alice_account.id(), transaction_request) + .await?; + + println!("Submitted a transaction with 4 P2ID notes. TX: {:?}", tx_id); + + println!("Submitting one more single P2ID transaction..."); + let init_seed: [u8; 15] = { + let mut init_seed = [0_u8; 15]; + client.rng().fill_bytes(&mut init_seed); + init_seed + }; + let target_account_id = AccountId::dummy( + init_seed, + AccountIdVersion::Version0, + AccountType::RegularAccountUpdatableCode, + AccountStorageMode::Public, + ); + + let send_amount = 50; + let fungible_asset = FungibleAsset::new(faucet_account.id(), send_amount).unwrap(); + + let p2id_note = create_p2id_note( + alice_account.id(), + target_account_id, + vec![fungible_asset.into()], + NoteType::Public, + Felt::new(0), + client.rng(), + )?; + + let transaction_request = TransactionRequestBuilder::new() + .own_output_notes(vec![OutputNote::Full(p2id_note)]) + .build() + .unwrap(); + + let tx_id = client + .submit_new_transaction(alice_account.id(), transaction_request) + .await?; + + println!("Submitted final P2ID transaction. TX: {:?}", tx_id); + + println!("\nAll steps completed successfully!"); + println!("Alice created a wallet, a faucet was deployed,"); + println!("5 notes of 100 tokens were minted to Alice, those notes were consumed,"); + println!("and then Alice sent 5 separate 50-token notes to 5 different users."); + + Ok(()) +} +``` + +Let's run the `src/main.rs` program again: + +```bash +cargo run --release +``` + +The output will look like this: + +```text +Latest block: 226896 + +[STEP 1] Creating a new account for Alice +Alice's account ID: "mtst1qp9czj052w3hvyqqqdtxmkh4myt45x2h" + +[STEP 2] Deploying a new fungible faucet. +Faucet account ID: "mtst1qrn8x36uckhhvgqqqdze8g6t7ggyufq0" + +[STEP 3] Minting 5 notes of 100 tokens each for Alice. +tx request built +Minted note #1 of 100 tokens for Alice. +tx request built +Minted note #2 of 100 tokens for Alice. +tx request built +Minted note #3 of 100 tokens for Alice. +tx request built +Minted note #4 of 100 tokens for Alice. +tx request built +Minted note #5 of 100 tokens for Alice. +All 5 notes minted for Alice successfully! + +[STEP 4] Alice will now consume all of her notes to consolidate them. +Currently, Alice has 2 consumable notes. Waiting... +Currently, Alice has 4 consumable notes. Waiting... +Found 5 consumable notes for Alice. Consuming them now... +All of Alice's notes consumed successfully. + +[STEP 5] Alice sends 5 notes of 50 tokens each to 5 different users. +Creating multiple P2ID notes for 4 target accounts in one transaction... +Submitted a transaction with 4 P2ID notes. +Submitting one more single P2ID transaction... + +All steps completed successfully! +Alice created a wallet, a faucet was deployed, +5 notes of 100 tokens were minted to Alice, those notes were consumed, +and then Alice sent 5 separate 50-token notes to 5 different users. +``` + +### Running the example + +To run a full working example navigate to the `rust-client` directory in the [miden-tutorials](https://github.com/0xMiden/miden-tutorials/) repository and run this command: + +```bash +cd rust-client +cargo run --release --bin create_mint_consume_send +``` + +### Continue learning + +Next tutorial: [Deploying a Counter Contract](counter_contract_tutorial.md) diff --git a/versioned_docs/version-0.12 (stable)/miden-tutorials/rust-client/network_transactions_tutorial.md b/versioned_docs/version-0.12 (stable)/miden-tutorials/rust-client/network_transactions_tutorial.md new file mode 100644 index 0000000..855b219 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-tutorials/rust-client/network_transactions_tutorial.md @@ -0,0 +1,807 @@ +--- +title: "Network Transactions on Miden" +sidebar_position: 6 +--- + +# Network Transactions on Miden + +_Using the Miden client in Rust to deploy and interact with smart contracts using network transactions_ + +## Overview + +In this tutorial, we will explore Network Transactions (NTXs) on Miden - a powerful feature that enables autonomous smart contract execution and public shared state management. Unlike local transactions that require users to execute and prove, network transactions are executed and proven by a network transaction builder. + +We'll build a network counter smart contract using the same MASM code as the regular counter, but with different storage configuration in Rust to enable network execution. + +## What we'll cover + +- Understanding Network Transactions and when to use them +- Deploying smart contracts with network storage mode +- Using transaction scripts to initialize network contracts on-chain +- Creating network notes for user interactions +- Validating network transaction results + +## Prerequisites + +This tutorial assumes you have completed the [counter contract tutorial](counter_contract_tutorial.md) and understand basic Miden assembly. + +## What are Network Transactions? + +Network transactions are executed and proven by the Miden operator rather than the client. They are useful for: + +- **Public shared state**: Multiple users can interact with the same contract state without race conditions +- **Autonomous execution**: Smart contracts can execute when conditions are met without user intervention +- **Resource-constrained devices**: Clients that can't generate ZK proofs efficiently +- **AMM applications**: Using network notes, you can build sophisticated AMMs where trades execute automatically + +The main trade-off is reduced privacy since the operator can see transaction inputs. + +## Step 1: Initialize your repository + +Create a new Rust repository for your Miden project and navigate to it: + +```bash +cargo new miden-network-transactions +cd miden-network-transactions +``` + +Add the following dependencies to your `Cargo.toml` file: + +```toml +[dependencies] +miden-client = { version = "0.12", features = ["testing", "tonic"] } +miden-client-sqlite-store = { version = "0.12", package = "miden-client-sqlite-store" } +miden-lib = { version = "0.12", default-features = false } +miden-objects = { version = "0.12", default-features = false, features = ["testing"] } +miden-crypto = { version = "0.17.1", features = ["executable"] } +miden-assembly = "0.18.3" +rand = { version = "0.9" } +serde = { version = "1", features = ["derive"] } +serde_json = { version = "1.0", features = ["raw_value"] } +tokio = { version = "1.46", features = ["rt-multi-thread", "net", "macros", "fs"] } +rand_chacha = "0.9.0" +``` + +## Step 2: Set up MASM files + +Create the directory structure: + +```bash +mkdir -p masm/accounts masm/scripts masm/notes +``` + +### Counter Contract + +We'll use the same counter contract MASM code as the regular counter tutorial. The key difference is in the Rust configuration, not the MASM code. + +Create `masm/accounts/counter.masm`: + +```masm +use.miden::active_account +use.miden::native_account +use.std::sys + +const.COUNTER_SLOT=0 + +#! Inputs: [] +#! Outputs: [count] +export.get_count + push.COUNTER_SLOT + # => [index] + + exec.active_account::get_item + # => [count] + + # clean up stack + movdn.4 dropw + # => [count] +end + +#! Inputs: [] +#! Outputs: [] +export.increment_count + push.COUNTER_SLOT + # => [index] + + exec.active_account::get_item + # => [count] + + add.1 + # => [count+1] + + debug.stack + + push.COUNTER_SLOT + # [index, count+1] + + exec.native_account::set_item + # => [OLD_VALUE] + + dropw + # => [] +end +``` + +### Transaction Script for Deployment + +Create `masm/scripts/counter_script.masm`: + +```masm +use.external_contract::counter_contract + +begin + call.counter_contract::increment_count +end +``` + +This script executes a function call (increment) that creates a necessary state change for our contract to be deployed and stored on the network on-chain. In Miden, network contracts must have their state modified through a transaction to be properly registered and committed to the blockchain - simply creating the account isn't sufficient for network storage mode. + +### Network Note for User Interaction + +Create `masm/notes/network_increment_note.masm`: + +```masm +use.external_contract::counter_contract + +begin + call.counter_contract::increment_count +end +``` + +After deployment, users will interact with the contract through these network notes. + +## Step 3: Initialize the client and create a user account + +Before deploying the network account and creating network notes, we need to set up the client and create a user account that will interact with our network contract. + +Copy and paste the following code into your `src/main.rs` file: + +```rust no_run +use std::{fs, path::Path, sync::Arc}; + +use miden_client::account::component::BasicWallet; +use miden_client::{ + address::NetworkId, + auth::AuthSecretKey, + builder::ClientBuilder, + crypto::FeltRng, + keystore::FilesystemKeyStore, + note::{ + Note, NoteAssets, NoteExecutionHint, NoteInputs, NoteMetadata, NoteRecipient, NoteTag, + NoteType, + }, + rpc::{Endpoint, GrpcClient}, + store::TransactionFilter, + transaction::{OutputNote, TransactionId, TransactionRequestBuilder, TransactionStatus}, + Client, ClientError, Felt, Word, +}; +use miden_client_sqlite_store::ClientBuilderSqliteExt; +use miden_lib::account::auth::{self, AuthRpoFalcon512}; +use miden_lib::transaction::TransactionKernel; +use miden_objects::{ + account::{AccountBuilder, AccountComponent, AccountStorageMode, AccountType, StorageSlot}, + assembly::{Assembler, DefaultSourceManager, Library, LibraryPath, Module, ModuleKind}, +}; +use rand::{rngs::StdRng, RngCore}; +use tokio::time::{sleep, Duration}; + +/// Waits for a specific transaction to be committed. +async fn wait_for_tx( + client: &mut Client>, + tx_id: TransactionId, +) -> Result<(), ClientError> { + loop { + client.sync_state().await?; + + // Check transaction status + let txs = client + .get_transactions(TransactionFilter::Ids(vec![tx_id])) + .await?; + let tx_committed = if !txs.is_empty() { + matches!(txs[0].status, TransactionStatus::Committed { .. }) + } else { + false + }; + + if tx_committed { + println!("✅ transaction {} committed", tx_id.to_hex()); + break; + } + + println!( + "Transaction {} not yet committed. Waiting...", + tx_id.to_hex() + ); + sleep(Duration::from_secs(2)).await; + } + Ok(()) +} + +/// Creates a Miden library from the provided account code and library path. +fn create_library( + account_code: String, + library_path: &str, +) -> Result> { + let assembler: Assembler = TransactionKernel::assembler().with_debug_mode(true); + let source_manager = Arc::new(DefaultSourceManager::default()); + let module = Module::parser(ModuleKind::Library).parse_str( + LibraryPath::new(library_path)?, + account_code, + &source_manager, + )?; + let library = assembler.clone().assemble_library([module])?; + Ok(library) +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Initialize client + let endpoint = Endpoint::testnet(); + let timeout_ms = 10_000; + let rpc_client = Arc::new(GrpcClient::new(&endpoint, timeout_ms)); + + // Initialize keystore + let keystore_path = std::path::PathBuf::from("./keystore"); + let keystore = Arc::new(FilesystemKeyStore::::new(keystore_path).unwrap()); + + let store_path = std::path::PathBuf::from("./store.sqlite3"); + + let mut client = ClientBuilder::new() + .rpc(rpc_client) + .sqlite_store(store_path) + .authenticator(keystore.clone()) + .in_debug_mode(true.into()) + .build() + .await?; + + let sync_summary = client.sync_state().await.unwrap(); + println!("Latest block: {}", sync_summary.block_num); + + // ------------------------------------------------------------------------- + // STEP 1: Create Basic User Account + // ------------------------------------------------------------------------- + println!("\n[STEP 1] Creating a new account for Alice"); + + // Account seed + let mut init_seed = [0_u8; 32]; + client.rng().fill_bytes(&mut init_seed); + + let key_pair = AuthSecretKey::new_rpo_falcon512(); + + // Build the account + let alice_account = AccountBuilder::new(init_seed) + .account_type(AccountType::RegularAccountUpdatableCode) + .storage_mode(AccountStorageMode::Public) + .with_auth_component(AuthRpoFalcon512::new(key_pair.public_key().to_commitment())) + .with_component(BasicWallet) + .build() + .unwrap(); + + // Add the account to the client + client.add_account(&alice_account, false).await?; + + // Add the key pair to the keystore + keystore.add_key(&key_pair).unwrap(); + + println!( + "Alice's account ID: {:?}", + alice_account.id().to_bech32(NetworkId::Testnet) + ); + + Ok(()) +} +``` + +This step initializes the Miden client and creates a basic user account (Alice) that will interact with our network contract. + +## Step 4: Create the network counter smart contract + +Now we'll create a network smart contract. The key difference from regular contracts is using `AccountStorageMode::Network` instead of `AccountStorageMode::Public`. + +Add this code to your `main()` function: + +```rust ignore +// ------------------------------------------------------------------------- +// STEP 2: Create Network Counter Smart Contract +// ------------------------------------------------------------------------- +println!("\n[STEP 2] Creating a network counter smart contract"); + +let counter_code = fs::read_to_string(Path::new("../masm/accounts/counter.masm")).unwrap(); + +// Create the network counter smart contract account +// First, compile the MASM code into an account component +let assembler: Assembler = TransactionKernel::assembler().with_debug_mode(true); +let counter_component = AccountComponent::compile( + &counter_code, + assembler.clone(), + vec![StorageSlot::Value([Felt::new(0); 4].into())], // Initialize counter storage to 0 +) +.unwrap() +.with_supports_all_types(); + +// Generate a random seed for the account +let mut init_seed = [0_u8; 32]; +client.rng().fill_bytes(&mut init_seed); + +// Build the immutable network account with no authentication +let counter_contract = AccountBuilder::new(init_seed) + .account_type(AccountType::RegularAccountImmutableCode) // Immutable code + .storage_mode(AccountStorageMode::Network) // Stored on network + .with_auth_component(auth::NoAuth) // No authentication required + .with_component(counter_component) + .build() + .unwrap(); + +client.add_account(&counter_contract, false).await.unwrap(); + +println!( + "contract id: {:?}", + counter_contract.id().to_bech32(NetworkId::Testnet) +); +``` + +This step creates a network smart contract with `AccountStorageMode::Network`, which enables the contract to be executed by the network operator. + +## Step 5: Deploy the network account with a transaction script + +We use a transaction script to deploy the network account and ensure it's properly registered on-chain. The script calls the `increment` function, which initializes the counter to 1. + +Add this code to your `main()` function: + +```rust ignore +// ------------------------------------------------------------------------- +// STEP 3: Deploy Network Account with Transaction Script +// ------------------------------------------------------------------------- +println!("\n[STEP 3] Deploy network counter smart contract"); + +let script_code = fs::read_to_string(Path::new("../masm/scripts/counter_script.masm")).unwrap(); + +let account_code = fs::read_to_string(Path::new("../masm/accounts/counter.masm")).unwrap(); +let library_path = "external_contract::counter_contract"; + +let library = create_library(account_code, library_path).unwrap(); + +let tx_script = client + .script_builder() + .with_dynamically_linked_library(&library)? + .compile_tx_script(&script_code)?; + +let tx_increment_request = TransactionRequestBuilder::new() + .custom_script(tx_script) + .build() + .unwrap(); + +let tx_id = client + .submit_new_transaction(counter_contract.id(), tx_increment_request) + .await + .unwrap(); + +println!( + "View transaction on MidenScan: https://testnet.midenscan.com/tx/{:?}", + tx_id +); + +// Wait for the transaction to be committed +wait_for_tx(&mut client, tx_id).await.unwrap(); +``` + +This step uses a transaction script to deploy the network account and ensure it's properly registered on-chain. The script calls the `increment` function, which initializes the counter to 1. + +## Step 6: Create a network note for user interaction + +We create a public note that the network operator can consume to execute the increment function. This increments the counter from 1 to 2. + +Add this code to your `main()` function: + +```rust ignore +// ------------------------------------------------------------------------- +// STEP 4: Prepare & Create the Network Note +// ------------------------------------------------------------------------- +println!("\n[STEP 4] Creating a network note for network counter contract"); + +let network_note_code = + fs::read_to_string(Path::new("../masm/notes/network_increment_note.masm")).unwrap(); +let account_code = fs::read_to_string(Path::new("../masm/accounts/counter.masm")).unwrap(); + +let library_path = "external_contract::counter_contract"; +let library = create_library(account_code, library_path).unwrap(); + +// Create and submit the network note that will increment the counter +// Generate a random serial number for the note +let serial_num = client.rng().draw_word(); + +// Compile the note script with the counter contract library +let note_script = client + .script_builder() + .with_dynamically_linked_library(&library)? + .compile_note_script(&network_note_code)?; + +// Create note recipient with empty inputs +let note_inputs = NoteInputs::new([].to_vec())?; +let recipient = NoteRecipient::new(serial_num, note_script, note_inputs); + +// Set up note metadata - tag it with the counter contract ID so it gets consumed +let tag = NoteTag::from_account_id(counter_contract.id()); +let metadata = NoteMetadata::new( + alice_account.id(), + NoteType::Public, + tag, + NoteExecutionHint::none(), + Felt::new(0), +)?; + +// Create the complete note +let increment_note = Note::new(NoteAssets::default(), metadata, recipient); + +// Build and submit the transaction containing the note +let note_req = TransactionRequestBuilder::new() + .own_output_notes(vec![OutputNote::Full(increment_note)]) + .build()?; + +let note_tx_id = client + .submit_new_transaction(alice_account.id(), note_req) + .await?; + +println!( + "View transaction on MidenScan: https://testnet.midenscan.com/tx/{:?}", + note_tx_id +); + +client.sync_state().await?; + +println!("network increment note creation tx submitted, waiting for onchain commitment"); + +// Wait for the note transaction to be committed +wait_for_tx(&mut client, note_tx_id).await.unwrap(); + +// Waiting for network note to be picked up by the network transaction builder +sleep(Duration::from_secs(6)).await; + +client.sync_state().await?; + +// Checking updated state +let new_account_state = client.get_account(counter_contract.id()).await.unwrap(); + +if let Some(account) = new_account_state.as_ref() { + let count: Word = account.account().storage().get_item(0).unwrap().into(); + let val = count.get(3).unwrap().as_int(); + assert_eq!(val, 2); + println!("🔢 Final counter value: {}", val); +} +``` + +This step creates a public note that the network operator can consume to execute the increment function. This increments the counter from 1 to 2. + +## Summary + +Your complete `main()` function should look like this: + +```rust +use std::{fs, path::Path, sync::Arc}; + +use miden_client::account::component::BasicWallet; +use miden_client::{ + address::NetworkId, + auth::AuthSecretKey, + builder::ClientBuilder, + crypto::FeltRng, + keystore::FilesystemKeyStore, + note::{ + Note, NoteAssets, NoteExecutionHint, NoteInputs, NoteMetadata, NoteRecipient, NoteTag, + NoteType, + }, + rpc::{Endpoint, GrpcClient}, + store::TransactionFilter, + transaction::{OutputNote, TransactionId, TransactionRequestBuilder, TransactionStatus}, + Client, ClientError, Felt, Word, +}; +use miden_client_sqlite_store::ClientBuilderSqliteExt; +use miden_lib::account::auth::{self, AuthRpoFalcon512}; +use miden_lib::transaction::TransactionKernel; +use miden_objects::{ + account::{AccountBuilder, AccountComponent, AccountStorageMode, AccountType, StorageSlot}, + assembly::{Assembler, DefaultSourceManager, Library, LibraryPath, Module, ModuleKind}, +}; +use rand::{rngs::StdRng, RngCore}; +use tokio::time::{sleep, Duration}; + +/// Waits for a specific transaction to be committed. +async fn wait_for_tx( + client: &mut Client>, + tx_id: TransactionId, +) -> Result<(), ClientError> { + loop { + client.sync_state().await?; + + // Check transaction status + let txs = client + .get_transactions(TransactionFilter::Ids(vec![tx_id])) + .await?; + let tx_committed = if !txs.is_empty() { + matches!(txs[0].status, TransactionStatus::Committed { .. }) + } else { + false + }; + + if tx_committed { + println!("✅ transaction {} committed", tx_id.to_hex()); + break; + } + + println!( + "Transaction {} not yet committed. Waiting...", + tx_id.to_hex() + ); + sleep(Duration::from_secs(2)).await; + } + Ok(()) +} + +/// Creates a Miden library from the provided account code and library path. +fn create_library( + account_code: String, + library_path: &str, +) -> Result> { + let assembler: Assembler = TransactionKernel::assembler().with_debug_mode(true); + let source_manager = Arc::new(DefaultSourceManager::default()); + let module = Module::parser(ModuleKind::Library).parse_str( + LibraryPath::new(library_path)?, + account_code, + &source_manager, + )?; + let library = assembler.clone().assemble_library([module])?; + Ok(library) +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Initialize client + let endpoint = Endpoint::testnet(); + let timeout_ms = 10_000; + let rpc_client = Arc::new(GrpcClient::new(&endpoint, timeout_ms)); + + // Initialize keystore + let keystore_path = std::path::PathBuf::from("./keystore"); + let keystore = Arc::new(FilesystemKeyStore::::new(keystore_path).unwrap()); + + let store_path = std::path::PathBuf::from("./store.sqlite3"); + + let mut client = ClientBuilder::new() + .rpc(rpc_client) + .sqlite_store(store_path) + .authenticator(keystore.clone()) + .in_debug_mode(true.into()) + .build() + .await?; + + let sync_summary = client.sync_state().await.unwrap(); + println!("Latest block: {}", sync_summary.block_num); + + // ------------------------------------------------------------------------- + // STEP 1: Create Basic User Account + // ------------------------------------------------------------------------- + println!("\n[STEP 1] Creating a new account for Alice"); + + // Account seed + let mut init_seed = [0_u8; 32]; + client.rng().fill_bytes(&mut init_seed); + + let key_pair = AuthSecretKey::new_rpo_falcon512(); + + // Build the account + let alice_account = AccountBuilder::new(init_seed) + .account_type(AccountType::RegularAccountUpdatableCode) + .storage_mode(AccountStorageMode::Public) + .with_auth_component(AuthRpoFalcon512::new(key_pair.public_key().to_commitment())) + .with_component(BasicWallet) + .build() + .unwrap(); + + // Add the account to the client + client.add_account(&alice_account, false).await?; + + // Add the key pair to the keystore + keystore.add_key(&key_pair).unwrap(); + + println!( + "Alice's account ID: {:?}", + alice_account.id().to_bech32(NetworkId::Testnet) + ); + + // ------------------------------------------------------------------------- + // STEP 2: Create Network Counter Smart Contract + // ------------------------------------------------------------------------- + println!("\n[STEP 2] Creating a network counter smart contract"); + + let counter_code = fs::read_to_string(Path::new("../masm/accounts/counter.masm")).unwrap(); + + // Create the network counter smart contract account + // First, compile the MASM code into an account component + let assembler: Assembler = TransactionKernel::assembler().with_debug_mode(true); + let counter_component = AccountComponent::compile( + &counter_code, + assembler.clone(), + vec![StorageSlot::Value([Felt::new(0); 4].into())], // Initialize counter storage to 0 + ) + .unwrap() + .with_supports_all_types(); + + // Generate a random seed for the account + let mut init_seed = [0_u8; 32]; + client.rng().fill_bytes(&mut init_seed); + + // Build the immutable network account with no authentication + let counter_contract = AccountBuilder::new(init_seed) + .account_type(AccountType::RegularAccountImmutableCode) // Immutable code + .storage_mode(AccountStorageMode::Network) // Stored on network + .with_auth_component(auth::NoAuth) // No authentication required + .with_component(counter_component) + .build() + .unwrap(); + + client.add_account(&counter_contract, false).await.unwrap(); + + println!( + "contract id: {:?}", + counter_contract.id().to_bech32(NetworkId::Testnet) + ); + + // ------------------------------------------------------------------------- + // STEP 3: Deploy Network Account with Transaction Script + // ------------------------------------------------------------------------- + println!("\n[STEP 3] Deploy network counter smart contract"); + + let script_code = fs::read_to_string(Path::new("../masm/scripts/counter_script.masm")).unwrap(); + + let account_code = fs::read_to_string(Path::new("../masm/accounts/counter.masm")).unwrap(); + let library_path = "external_contract::counter_contract"; + + let library = create_library(account_code, library_path).unwrap(); + + let tx_script = client + .script_builder() + .with_dynamically_linked_library(&library)? + .compile_tx_script(&script_code)?; + + let tx_increment_request = TransactionRequestBuilder::new() + .custom_script(tx_script) + .build() + .unwrap(); + + let tx_id = client + .submit_new_transaction(counter_contract.id(), tx_increment_request) + .await + .unwrap(); + + println!( + "View transaction on MidenScan: https://testnet.midenscan.com/tx/{:?}", + tx_id + ); + + // Wait for the transaction to be committed + wait_for_tx(&mut client, tx_id).await.unwrap(); + + // ------------------------------------------------------------------------- + // STEP 4: Prepare & Create the Network Note + // ------------------------------------------------------------------------- + println!("\n[STEP 4] Creating a network note for network counter contract"); + + let network_note_code = + fs::read_to_string(Path::new("../masm/notes/network_increment_note.masm")).unwrap(); + let account_code = fs::read_to_string(Path::new("../masm/accounts/counter.masm")).unwrap(); + + let library_path = "external_contract::counter_contract"; + let library = create_library(account_code, library_path).unwrap(); + + // Create and submit the network note that will increment the counter + // Generate a random serial number for the note + let serial_num = client.rng().draw_word(); + + // Compile the note script with the counter contract library + let note_script = client + .script_builder() + .with_dynamically_linked_library(&library)? + .compile_note_script(&network_note_code)?; + + // Create note recipient with empty inputs + let note_inputs = NoteInputs::new([].to_vec())?; + let recipient = NoteRecipient::new(serial_num, note_script, note_inputs); + + // Set up note metadata - tag it with the counter contract ID so it gets consumed + let tag = NoteTag::from_account_id(counter_contract.id()); + let metadata = NoteMetadata::new( + alice_account.id(), + NoteType::Public, + tag, + NoteExecutionHint::none(), + Felt::new(0), + )?; + + // Create the complete note + let increment_note = Note::new(NoteAssets::default(), metadata, recipient); + + // Build and submit the transaction containing the note + let note_req = TransactionRequestBuilder::new() + .own_output_notes(vec![OutputNote::Full(increment_note)]) + .build()?; + + let note_tx_id = client + .submit_new_transaction(alice_account.id(), note_req) + .await?; + + println!( + "View transaction on MidenScan: https://testnet.midenscan.com/tx/{:?}", + note_tx_id + ); + + client.sync_state().await?; + + println!("network increment note creation tx submitted, waiting for onchain commitment"); + + // Wait for the note transaction to be committed + wait_for_tx(&mut client, note_tx_id).await.unwrap(); + + // Waiting for network note to be picked up by the network transaction builder + sleep(Duration::from_secs(6)).await; + + client.sync_state().await?; + + // Checking updated state + let new_account_state = client.get_account(counter_contract.id()).await.unwrap(); + + if let Some(account) = new_account_state.as_ref() { + let count: Word = account.account().storage().get_item(0).unwrap().into(); + let val = count.get(3).unwrap().as_int(); + assert_eq!(val, 2); + println!("🔢 Final counter value: {}", val); + } + + Ok(()) +} +``` + +## Step 7: Running the Example + +To run the complete network transaction example: + +```bash +cd rust-client +cargo run --release --bin network_notes_counter_contract +``` + +Expected output: + +```text +Latest block: 508977 + +[STEP 1] Creating a new account for Alice +Alice's account ID: "mtst1qrpk3gmyv2p06ypgh7gss9hs0gl80gwl" + +[STEP 2] Creating a network counter smart contract +contract id: "mtst1qz95e5k55xeh5sz2zann5xtp4uq9hpht" + +[STEP 3] Deploy network counter smart contract +View transaction on MidenScan: https://testnet.midenscan.com/tx/0xbe8dddab0403544a28c9a24d0400837cfd639b030670cf436ba113261fbdfce0 +✅ transaction 0xbe8dddab0403544a28c9a24d0400837cfd639b030670cf436ba113261fbdfce0 committed + +[STEP 4] Creating a network note for network counter contract +View transaction on MidenScan: https://testnet.midenscan.com/tx/0x0bb5f6b786eb0f129d944975e3fae226084441eaf422f187657afbd74641327c +network increment note created, waiting for onchain commitment +✅ transaction 0x0bb5f6b786eb0f129d944975e3fae226084441eaf422f187657afbd74641327c committed +🔢 Final counter value: 2 +``` + +## Summary + +Network transactions on Miden enable powerful use cases by allowing the operator to execute transactions on behalf of users. The key steps are: + +1. **Create user account**: Standard account creation for interaction +2. **Create network account**: Use `AccountStorageMode::Network` instead of `Public` +3. **Deploy with transaction script**: Ensures the contract is registered on-chain +4. **Interact with network notes**: Users create public notes that the operator executes + +The same MASM code works for both regular and network contracts - the difference is purely in the Rust configuration. This makes network transactions a powerful tool for building applications like AMMs where multiple users need to interact with shared state efficiently. + +### Continue learning + +Next tutorial: [How To Create Notes with Custom Logic](custom_note_how_to.md) diff --git a/versioned_docs/version-0.12 (stable)/miden-tutorials/rust-client/oracle_tutorial.md b/versioned_docs/version-0.12 (stable)/miden-tutorials/rust-client/oracle_tutorial.md new file mode 100644 index 0000000..040f514 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-tutorials/rust-client/oracle_tutorial.md @@ -0,0 +1,394 @@ +--- +title: "Consuming On-Chain Price Data from the Pragma Oracle" +sidebar_position: 13 +--- + +# Consuming On-Chain Price Data from the Pragma Oracle + +_Using the Pragma oracle to get on chain price data_ + +## Overview + +In this tutorial, we will build a simple “price reader” smart contract that will read Bitcoin price data from the on-chain Pragma oracle. + +We will use a script to call the `read_price` function in our "price reader" smart contract, which, in turn, calls the Pragma oracle via foreign procedure invocation (FPI). This tutorial lays the foundation for how you can integrate on-chain price data into your DeFi applications on Miden. + +## What we'll cover + +- Deploying a smart contract that can read oracle price data +- Using foreign procedure invocation to get real time on-chain price data + +## Prerequisites + +This tutorial assumes you have a basic understanding of Miden assembly, have completed the previous tutorials on using the Rust client, and have completed the tutorial on foreign procedure invocation. + +To quickly get up to speed with Miden assembly (MASM), please play around with running Miden programs in the [Miden playground](https://0xMiden.github.io/examples/). + +## Step 1: Initialize your repository + +Create a new Rust repository for your Miden project and navigate to it with the following command: + +```bash +cargo new miden-defi-app +cd miden-defi-app +``` + +Add the following dependencies to your `Cargo.toml` file: + +```toml +[dependencies] +miden-client = { version = "0.12", features = ["testing", "tonic"] } +miden-client-sqlite-store = { version = "0.12", package = "miden-client-sqlite-store" } +miden-lib = { version = "0.12", default-features = false } +miden-objects = { version = "0.12", default-features = false, features = ["testing"] } +miden-crypto = { version = "0.17.1", features = ["executable"] } +miden-assembly = "0.18.3" +rand = { version = "0.9" } +serde = { version = "1", features = ["derive"] } +serde_json = { version = "1.0", features = ["raw_value"] } +tokio = { version = "1.46", features = ["rt-multi-thread", "net", "macros", "fs"] } +rand_chacha = "0.9.0" +``` + +### Step 1: Set up your `src/main.rs` file + +Copy and paste the following code into your `src/main.rs` file: + +```rust +use miden_client::{ + assembly::{Assembler, DefaultSourceManager, LibraryPath, Module, ModuleKind}, + builder::ClientBuilder, + keystore::FilesystemKeyStore, + rpc::{ + domain::account::{AccountStorageRequirements, StorageMapKey}, + Endpoint, GrpcClient, + }, + transaction::{ForeignAccount, TransactionRequestBuilder}, + Client, ClientError, +}; +use miden_client_sqlite_store::ClientBuilderSqliteExt; +use miden_lib::{account::auth::NoAuth, transaction::TransactionKernel}; +use miden_objects::{ + account::{AccountComponent, AccountId, AccountStorageMode, AccountType, StorageSlot}, + Felt, Word, ZERO, +}; +use rand::{rngs::StdRng, RngCore}; +use std::{fs, path::Path, sync::Arc}; + +/// Import the oracle + its publishers and return the ForeignAccount list +/// Due to Pragma's decentralized oracle architecture, we need to get the +/// list of all data publisher accounts to read price from via a nested FPI call +pub async fn get_oracle_foreign_accounts( + client: &mut Client>, + oracle_account_id: AccountId, + trading_pair: u64, +) -> Result, ClientError> { + client.import_account_by_id(oracle_account_id).await?; + + let oracle_record = client + .get_account(oracle_account_id) + .await + .expect("RPC failed") + .expect("oracle account not found"); + + let storage = oracle_record.account().storage(); + let publisher_count = storage.get_item(1).unwrap()[0].as_int(); + + let publisher_ids: Vec = (1..publisher_count.saturating_sub(1)) + .map(|i| { + let digest = storage.get_item(2 + i as u8).unwrap(); + let words: Word = digest.into(); + AccountId::new_unchecked([words[3], words[2]]) + }) + .collect(); + + let mut foreign_accounts = Vec::with_capacity(publisher_ids.len() + 1); + + for pid in publisher_ids { + client.import_account_by_id(pid).await?; + + foreign_accounts.push(ForeignAccount::public( + pid, + AccountStorageRequirements::new([( + 1u8, + &[StorageMapKey::from([ + ZERO, + ZERO, + ZERO, + Felt::new(trading_pair), + ])], + )]), + )?); + } + + foreign_accounts.push(ForeignAccount::public( + oracle_account_id, + AccountStorageRequirements::default(), + )?); + + Ok(foreign_accounts) +} + +fn create_library( + assembler: Assembler, + library_path: &str, + source_code: &str, +) -> Result> { + let source_manager = Arc::new(DefaultSourceManager::default()); + let module = Module::parser(ModuleKind::Library).parse_str( + LibraryPath::new(library_path)?, + source_code, + &source_manager, + )?; + let library = assembler.clone().assemble_library([module])?; + Ok(library) +} + +#[tokio::main] +async fn main() -> Result<(), ClientError> { + // ------------------------------------------------------------------------- + // Initialize Client + // ------------------------------------------------------------------------- + let endpoint = Endpoint::testnet(); + let timeout_ms = 10_000; + let rpc_client = Arc::new(GrpcClient::new(&endpoint, timeout_ms)); + + let keystore_path = std::path::PathBuf::from("./keystore"); + let keystore = Arc::new(FilesystemKeyStore::::new(keystore_path).unwrap()); + + let store_path = std::path::PathBuf::from("./store.sqlite3"); + + let mut client = ClientBuilder::new() + .rpc(rpc_client) + .sqlite_store(store_path) + .authenticator(keystore.clone()) + .in_debug_mode(true.into()) + .build() + .await?; + + println!("Latest block: {}", client.sync_state().await?.block_num); + + // ------------------------------------------------------------------------- + // Get all foreign accounts for oracle data + // ------------------------------------------------------------------------- + let oracle_bech32 = "mtst1qq0zffxzdykm7qqqqdt24cc2du5ghx99"; + let (_, oracle_account_id) = AccountId::from_bech32(oracle_bech32).unwrap(); + let btc_usd_pair_id = 120195681; + let foreign_accounts: Vec = + get_oracle_foreign_accounts(&mut client, oracle_account_id, btc_usd_pair_id).await?; + + println!( + "Oracle accountId prefix: {:?} suffix: {:?}", + oracle_account_id.prefix(), + oracle_account_id.suffix() + ); + + // ------------------------------------------------------------------------- + // Create Oracle Reader contract + // ------------------------------------------------------------------------- + let contract_code = + fs::read_to_string(Path::new("../masm/accounts/oracle_reader.masm")).unwrap(); + + let assembler = TransactionKernel::assembler().with_debug_mode(true); + + let contract_component = AccountComponent::compile( + &contract_code, + assembler, + vec![StorageSlot::Value(Word::default())], + ) + .unwrap() + .with_supports_all_types(); + + let mut seed = [0_u8; 32]; + client.rng().fill_bytes(&mut seed); + + let oracle_reader_contract = miden_objects::account::AccountBuilder::new(seed) + .account_type(AccountType::RegularAccountImmutableCode) + .storage_mode(AccountStorageMode::Public) + .with_component(contract_component.clone()) + .with_auth_component(NoAuth) + .build() + .unwrap(); + + client + .add_account(&oracle_reader_contract, false) + .await + .unwrap(); + + // ------------------------------------------------------------------------- + // Build the script that calls our `get_price` procedure + // ------------------------------------------------------------------------- + let script_path = Path::new("../masm/scripts/oracle_reader_script.masm"); + let script_code = fs::read_to_string(script_path).unwrap(); + + let assembler = TransactionKernel::assembler().with_debug_mode(true); + let library_path = "external_contract::oracle_reader"; + let account_component_lib = + create_library(assembler.clone(), library_path, &contract_code).unwrap(); + + let tx_script = client + .script_builder() + .with_dynamically_linked_library(&account_component_lib) + .unwrap() + .compile_tx_script(&script_code) + .unwrap(); + + let tx_increment_request = TransactionRequestBuilder::new() + .foreign_accounts(foreign_accounts) + .custom_script(tx_script) + .build() + .unwrap(); + + let tx_id = client + .submit_new_transaction(oracle_reader_contract.id(), tx_increment_request) + .await + .unwrap(); + + println!( + "View transaction on MidenScan: https://testnet.midenscan.com/tx/{:?}", + tx_id + ); + + client.sync_state().await.unwrap(); + + Ok(()) +} +``` + +_Don't run this code just yet, we still need to create our smart contract that queries the oracle_ + +In the code above, we specified the Pragma oracle account id `0x4f67e78643022e00000220d8997e33` and the BTC/USD pair `120195681`. The `get_oracle_foreign_accounts` function returns all of the `ForeignAccounts` that you will need to execute the transaction to get the price data from the oracle. Since Pragma's oracle depends on multiple publishers, this function queries all of the publisher account ids required to make a successful FPI call. + +To learn more about Pragma's oracle architecture, you can look at the source code here: https://github.com/astraly-labs/pragma-miden + +## Step 2: Build the price reader smart contract and script + +Just like in previous tutorials, for better code organization we will separate the Miden assembly code from our Rust code. + +Create a directory named `masm` at the **root** of your `miden-counter-contract` directory. This will contain our contract and script masm code. + +Initialize the `masm` directory: + +```bash +mkdir -p masm/accounts masm/scripts +``` + +This will create: + +``` +masm/ +├── accounts/ +└── scripts/ +``` + +### Oracle price reader smart contract + +Below is our oracle price reader contract. It has a a single exported procedure: `get_price` + +The import `miden::tx` contains the `tx::execute_foreign_procedure` which we will use to read the price from the oracle contract. + +#### Here's a breakdown of what the `get_price` procedure does: + +1. Pushes `0.0.0.120195681` onto the stack, representing the BTC/USD pair in the Pragma oracle. +2. Pushes `0xb86237a8c9cd35acfef457e47282cc4da43df676df410c988eab93095d8fb3b9` onto the stack which is the procedure root of the `get_median` procedure in the oracle. +3. Pushes `599064613630720.5721796415433354752` onto the stack which is the oracle id prefix and suffix. +4. Calls `tx::execute_foreign_procedure` which calls the `get_median` procedure via foreign procedure invocation. + +Inside of the `masm/accounts/` directory, create the `oracle_reader.masm` file: + +```masm +use.miden::tx + +# Fetches the current price from the `get_median` +# procedure from the Pragma oracle +# => [] +export.get_price + push.0.0.0.120195681 + # => [PAIR] + + # This is the procedure root of the `get_median` procedure + push.0xb86237a8c9cd35acfef457e47282cc4da43df676df410c988eab93095d8fb3b9 + # => [GET_MEDIAN_HASH, PAIR] + + push.939716883672832.2172042075194638080 + # => [oracle_id_prefix, oracle_id_suffix, GET_MEDIAN_HASH, PAIR] + + exec.tx::execute_foreign_procedure + # => [price] + + debug.stack + # => [price] + + dropw dropw +end +``` + +**Note**: _It's a good habit to add comments above each line of MASM code with the expected stack state. This improves readability and helps with debugging._ + +### Create the script which calls the `get_price` procedure + +This is a Miden assembly script that will call the `get_price` procedure during the transaction. + +Inside of the `masm/scripts/` directory, create the `oracle_reader_script.masm` file: + +```masm +use.external_contract::oracle_reader + +begin + exec.oracle_reader::get_price +end +``` + +## Step 3: Run the program + +Run the following command to execute src/main.rs: + +``` +cargo run --release +``` + +The output of our program will look something like this: + +``` +cleared sqlite store: ./store.sqlite3 +Latest block: 648397 +Oracle accountId prefix: V0(AccountIdPrefixV0 { prefix: 5721796415433354752 }) suffix: 599064613630720 +Stack state before step 8766: +├── 0: 82655190335 +├── 1: 0 +├── 2: 0 +├── 3: 0 +├── 4: 0 +├── 5: 0 +├── 6: 0 +├── 7: 0 +├── 8: 0 +├── 9: 0 +├── 10: 0 +├── 11: 0 +├── 12: 0 +├── 13: 0 +├── 14: 0 +├── 15: 0 +├── 16: 0 +├── 17: 0 +├── 18: 0 +└── 19: 0 + +View transaction on MidenScan: https://testnet.midenscan.com/tx/0xc8951190564d5c3ac59fe99d8911f8c17f5b59ba542e2eb860413898902f3722 +``` + +As you can see, at the top of the stack is the price returned from the Pragma oracle. The price is returned with 6 decimal places. Currently Pragma only publishes the `BTC/USD` price feed on testnet. + +### Running the example + +To run the full example, navigate to the `rust-client` directory in the [miden-tutorials](https://github.com/0xMiden/miden-tutorials/) repository and run this command: + +```bash +cd rust-client +cargo run --release --bin oracle_data_query +``` + +### Continue learning + +Next tutorial: [How to Use Unauthenticated Notes](./unauthenticated_note_how_to.md) diff --git a/versioned_docs/version-0.12 (stable)/miden-tutorials/rust-client/public_account_interaction_tutorial.md b/versioned_docs/version-0.12 (stable)/miden-tutorials/rust-client/public_account_interaction_tutorial.md new file mode 100644 index 0000000..81c7e60 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-tutorials/rust-client/public_account_interaction_tutorial.md @@ -0,0 +1,511 @@ +--- +title: "Interacting with Public Smart Contracts" +sidebar_position: 5 +--- + +# Interacting with Public Smart Contracts + +_Using the Miden client in Rust to interact with public smart contracts on Miden_ + +## Overview + +In the previous tutorial, we built a simple counter contract and deployed it to the Miden testnet. However, we only covered how the contract’s deployer could interact with it. Now, let’s explore how anyone can interact with a public smart contract on Miden. + +We’ll retrieve the counter contract’s state from the chain and rebuild it locally so a local transaction can be executed against it. In the near future, Miden will support network transactions, making the process of submitting transactions to public smart contracts much more like traditional blockchains. + +Just like in the previous tutorial, we will use a script to invoke the increment function within the counter contract to update the count. However, this tutorial demonstrates how to call a procedure in a smart contract that was deployed by a different user on Miden. + +## What we'll cover + +- Reading state from a public smart contract +- Interacting with public smart contracts on Miden + +## Prerequisites + +This tutorial assumes you have a basic understanding of Miden assembly and completed the previous tutorial on deploying the counter contract. Although not a requirement, it is recommended to complete the counter contract deployment tutorial before starting this tutorial. + +## Step 1: Initialize your repository + +Create a new Rust repository for your Miden project and navigate to it with the following command: + +```bash +cargo new miden-counter-contract +cd miden-counter-contract +``` + +Add the following dependencies to your `Cargo.toml` file: + +```toml +[dependencies] +miden-client = { version = "0.12", features = ["testing", "tonic"] } +miden-client-sqlite-store = { version = "0.12", package = "miden-client-sqlite-store" } +miden-lib = { version = "0.12", default-features = false } +miden-objects = { version = "0.12", default-features = false, features = ["testing"] } +miden-crypto = { version = "0.17.1", features = ["executable"] } +miden-assembly = "0.18.3" +rand = { version = "0.9" } +serde = { version = "1", features = ["derive"] } +serde_json = { version = "1.0", features = ["raw_value"] } +tokio = { version = "1.46", features = ["rt-multi-thread", "net", "macros", "fs"] } +rand_chacha = "0.9.0" +``` + +## Step 2: Build the counter contract + +For better code organization, we will separate the Miden assembly code from our Rust code. + +Create a directory named `masm` at the **root** of your `miden-counter-contract` directory. This will contain our contract and script masm code. + +Initialize the `masm` directory: + +```bash +mkdir -p masm/accounts masm/scripts +``` + +This will create: + +```text +masm/ +├── accounts/ +└── scripts/ +``` + +Inside of the `masm/accounts/` directory, create the `counter.masm` file: + +```masm +use.miden::active_account +use.miden::native_account +use.std::sys + +const.COUNTER_SLOT=0 + +#! Inputs: [] +#! Outputs: [count] +export.get_count + push.COUNTER_SLOT + # => [index] + + exec.active_account::get_item + # => [count] + + # clean up stack + movdn.4 dropw + # => [count] +end + +#! Inputs: [] +#! Outputs: [] +export.increment_count + push.COUNTER_SLOT + # => [index] + + exec.active_account::get_item + # => [count] + + add.1 + # => [count+1] + + debug.stack + + push.COUNTER_SLOT + # [index, count+1] + + exec.native_account::set_item + # => [OLD_VALUE] + + dropw + # => [] +end +``` + +Inside of the `masm/scripts/` directory, create the `counter_script.masm` file: + +```masm +use.external_contract::counter_contract + +begin + call.counter_contract::increment_count +end +``` + +**Note**: _We explained in the previous counter contract tutorial what exactly happens at each step in the `increment_count` procedure._ + +### Step 3: Set up your `src/main.rs` file + +Copy and paste the following code into your `src/main.rs` file: + +```rust no_run +use miden_lib::transaction::TransactionKernel; +use rand::rngs::StdRng; +use std::{fs, path::Path, sync::Arc}; + +use miden_client::{ + account::AccountId, + assembly::{Assembler, DefaultSourceManager, LibraryPath, Module, ModuleKind}, + builder::ClientBuilder, + keystore::FilesystemKeyStore, + rpc::{Endpoint, GrpcClient}, + transaction::TransactionRequestBuilder, + ClientError, +}; +use miden_client_sqlite_store::ClientBuilderSqliteExt; + +fn create_library( + assembler: Assembler, + library_path: &str, + source_code: &str, +) -> Result> { + let source_manager = Arc::new(DefaultSourceManager::default()); + let module = Module::parser(ModuleKind::Library).parse_str( + LibraryPath::new(library_path)?, + source_code, + &source_manager, + )?; + let library = assembler.clone().assemble_library([module])?; + Ok(library) +} + +#[tokio::main] +async fn main() -> Result<(), ClientError> { + // Initialize client + let endpoint = Endpoint::testnet(); + let timeout_ms = 10_000; + let rpc_client = Arc::new(GrpcClient::new(&endpoint, timeout_ms)); + + // Initialize keystore + let keystore_path = std::path::PathBuf::from("./keystore"); + let keystore = Arc::new(FilesystemKeyStore::::new(keystore_path).unwrap()); + + let store_path = std::path::PathBuf::from("./store.sqlite3"); + + let mut client = ClientBuilder::new() + .rpc(rpc_client) + .sqlite_store(store_path) + .authenticator(keystore.clone()) + .in_debug_mode(true.into()) + .build() + .await?; + + let sync_summary = client.sync_state().await.unwrap(); + println!("Latest block: {}", sync_summary.block_num); + + Ok(()) +} +``` + +## Step 4: Reading public state from a smart contract + +To read the public storage state of a smart contract on Miden we either instantiate the `TonicRpcClient` by itself, or use the `test_rpc_api()` method on the `Client` instance. In this example, we will be using the `test_rpc_api()` method. + +We will be reading the public storage state of the counter contract deployed on the testnet at address `0x303dd027d27adc0000012b07dbf1b4`. + +Add the following code snippet to the end of your `src/main.rs` function: + +```rust ignore +// ------------------------------------------------------------------------- +// STEP 1: Read the Public State of the Counter Contract +// ------------------------------------------------------------------------- +println!("\n[STEP 1] Reading data from public state"); + +// Define the Counter Contract account id from counter contract deploy +let (_, counter_contract_id) = + AccountId::from_bech32("mtst1arjemrxne8lj5qz4mg9c8mtyxg954483").unwrap(); + +client + .import_account_by_id(counter_contract_id) + .await + .unwrap(); + +let counter_contract_details = client.get_account(counter_contract_id).await.unwrap(); + +let counter_contract = if let Some(account_record) = counter_contract_details { + // Clone the account to get an owned instance + let account = account_record.account().clone(); + println!( + "Account details: {:?}", + account.storage().slots().first().unwrap() + ); + account // Now returns an owned account +} else { + panic!("Counter contract not found!"); +}; +``` + +Run the following command to execute src/main.rs: + +```bash +cargo run --release +``` + +After the program executes, you should see the counter contract count value and nonce printed to the terminal, for example: + +```text +count val: [0, 0, 0, 5] +counter nonce: 5 +``` + +## Step 5: Importing a public account + +Add the following code snippet to the end of your `src/main.rs` function: + +```rust ignore +// ------------------------------------------------------------------------- +// STEP 2: Call the Counter Contract with a script +// ------------------------------------------------------------------------- +println!("\n[STEP 2] Call the increment_count procedure in the counter contract"); + +// Load the MASM script referencing the increment procedure +let script_path = Path::new("../masm/scripts/counter_script.masm"); +let script_code = fs::read_to_string(script_path).unwrap(); + +let counter_path = Path::new("../masm/accounts/counter.masm"); +let counter_code = fs::read_to_string(counter_path).unwrap(); + +let assembler = TransactionKernel::assembler().with_debug_mode(true); +let account_component_lib = create_library( + assembler.clone(), + "external_contract::counter_contract", + &counter_code, +) +.unwrap(); + +let tx_script = client + .script_builder() + .with_dynamically_linked_library(&account_component_lib) + .unwrap() + .compile_tx_script(&script_code) + .unwrap(); + +// Build a transaction request with the custom script +let tx_increment_request = TransactionRequestBuilder::new() + .custom_script(tx_script) + .build() + .unwrap(); + +// Execute and submit the transaction +let tx_id = client + .submit_new_transaction(counter_contract.id(), tx_increment_request) + .await + .unwrap(); + +println!( + "View transaction on MidenScan: https://testnet.midenscan.com/tx/{:?}", + tx_id +); + +client.sync_state().await.unwrap(); + +// Retrieve updated contract data to see the incremented counter +let account = client.get_account(counter_contract.id()).await.unwrap(); +println!( + "counter contract storage: {:?}", + account.unwrap().account().storage().get_item(0) +); +``` + +## Summary + +The final `src/main.rs` file should look like this: + +```rust +use miden_lib::transaction::TransactionKernel; +use rand::rngs::StdRng; +use std::{fs, path::Path, sync::Arc}; + +use miden_client::{ + account::AccountId, + assembly::{Assembler, DefaultSourceManager, LibraryPath, Module, ModuleKind}, + builder::ClientBuilder, + keystore::FilesystemKeyStore, + rpc::{Endpoint, GrpcClient}, + transaction::TransactionRequestBuilder, + ClientError, +}; +use miden_client_sqlite_store::ClientBuilderSqliteExt; + +fn create_library( + assembler: Assembler, + library_path: &str, + source_code: &str, +) -> Result> { + let source_manager = Arc::new(DefaultSourceManager::default()); + let module = Module::parser(ModuleKind::Library).parse_str( + LibraryPath::new(library_path)?, + source_code, + &source_manager, + )?; + let library = assembler.clone().assemble_library([module])?; + Ok(library) +} + +#[tokio::main] +async fn main() -> Result<(), ClientError> { + // Initialize client + let endpoint = Endpoint::testnet(); + let timeout_ms = 10_000; + let rpc_client = Arc::new(GrpcClient::new(&endpoint, timeout_ms)); + + // Initialize keystore + let keystore_path = std::path::PathBuf::from("./keystore"); + let keystore = Arc::new(FilesystemKeyStore::::new(keystore_path).unwrap()); + + let store_path = std::path::PathBuf::from("./store.sqlite3"); + + let mut client = ClientBuilder::new() + .rpc(rpc_client) + .sqlite_store(store_path) + .authenticator(keystore.clone()) + .in_debug_mode(true.into()) + .build() + .await?; + + let sync_summary = client.sync_state().await.unwrap(); + println!("Latest block: {}", sync_summary.block_num); + + // ------------------------------------------------------------------------- + // STEP 1: Read the Public State of the Counter Contract + // ------------------------------------------------------------------------- + println!("\n[STEP 1] Reading data from public state"); + + // Define the Counter Contract account id from counter contract deploy + let (_, counter_contract_id) = + AccountId::from_bech32("mtst1arjemrxne8lj5qz4mg9c8mtyxg954483").unwrap(); + + client + .import_account_by_id(counter_contract_id) + .await + .unwrap(); + + let counter_contract_details = client.get_account(counter_contract_id).await.unwrap(); + + let counter_contract = if let Some(account_record) = counter_contract_details { + // Clone the account to get an owned instance + let account = account_record.account().clone(); + println!( + "Account details: {:?}", + account.storage().slots().first().unwrap() + ); + account // Now returns an owned account + } else { + panic!("Counter contract not found!"); + }; + + // ------------------------------------------------------------------------- + // STEP 2: Call the Counter Contract with a script + // ------------------------------------------------------------------------- + println!("\n[STEP 2] Call the increment_count procedure in the counter contract"); + + // Load the MASM script referencing the increment procedure + let script_path = Path::new("../masm/scripts/counter_script.masm"); + let script_code = fs::read_to_string(script_path).unwrap(); + + let counter_path = Path::new("../masm/accounts/counter.masm"); + let counter_code = fs::read_to_string(counter_path).unwrap(); + + let assembler = TransactionKernel::assembler().with_debug_mode(true); + let account_component_lib = create_library( + assembler.clone(), + "external_contract::counter_contract", + &counter_code, + ) + .unwrap(); + + let tx_script = client + .script_builder() + .with_dynamically_linked_library(&account_component_lib) + .unwrap() + .compile_tx_script(&script_code) + .unwrap(); + + // Build a transaction request with the custom script + let tx_increment_request = TransactionRequestBuilder::new() + .custom_script(tx_script) + .build() + .unwrap(); + + // Execute and submit the transaction + let tx_id = client + .submit_new_transaction(counter_contract.id(), tx_increment_request) + .await + .unwrap(); + + println!( + "View transaction on MidenScan: https://testnet.midenscan.com/tx/{:?}", + tx_id + ); + + client.sync_state().await.unwrap(); + + // Retrieve updated contract data to see the incremented counter + let account = client.get_account(counter_contract.id()).await.unwrap(); + println!( + "counter contract storage: {:?}", + account.unwrap().account().storage().get_item(0) + ); + Ok(()) +} +``` + +Run the following command to execute src/main.rs: + +```bash +cargo run --release +``` + +The output of our program will look something like this depending on the current count value in the smart contract: + +```text +Client initialized successfully. +Latest block: 242342 + +[STEP 1] Building counter contract from public state +count val: [0, 0, 0, 1] +counter nonce: 1 + +[STEP 2] Call the increment_count procedure in the counter contract +Procedure 1: "0x92495ca54d519eb5e4ba22350f837904d3895e48d74d8079450f19574bb84cb6" +Procedure 2: "0xecd7eb223a5524af0cc78580d96357b298bb0b3d33fe95aeb175d6dab9de2e54" +number of procedures: 2 +Final script: +begin + # => [] + call.0xecd7eb223a5524af0cc78580d96357b298bb0b3d33fe95aeb175d6dab9de2e54 +end +Stack state before step 1812: +├── 0: 2 +├── 1: 0 +├── 2: 0 +├── 3: 0 +├── 4: 0 +├── 5: 0 +├── 6: 0 +├── 7: 0 +├── 8: 0 +├── 9: 0 +├── 10: 0 +├── 11: 0 +├── 12: 0 +├── 13: 0 +├── 14: 0 +├── 15: 0 +├── 16: 0 +├── 17: 0 +├── 18: 0 +└── 19: 0 + +View transaction on MidenScan: https://testnet.midenscan.com/tx/0x8183aed150f20b9c26d4cb7840bfc92571ea45ece31116170b11cdff2649eb5c +counter contract storage: Ok(RpoDigest([0, 0, 0, 2])) +``` + +### Running the example + +To run the full example, navigate to the `rust-client` directory in the [miden-tutorials](https://github.com/0xMiden/miden-tutorials/) repository and run this command: + +```bash +cd rust-client +cargo run --release --bin counter_contract_increment +``` + +### Continue learning + +Next tutorial: [Network Transactions on Miden](network_transactions_tutorial.md) diff --git a/versioned_docs/version-0.12 (stable)/miden-tutorials/rust-client/unauthenticated_note_how_to.md b/versioned_docs/version-0.12 (stable)/miden-tutorials/rust-client/unauthenticated_note_how_to.md new file mode 100644 index 0000000..075ec46 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-tutorials/rust-client/unauthenticated_note_how_to.md @@ -0,0 +1,468 @@ +--- +title: "How to Use Unauthenticated Notes" +sidebar_position: 9 +--- + +# How to Use Unauthenticated Notes + +_Using unauthenticated notes for optimistic note consumption_ + +## Overview + +In this guide, we will explore how to leverage unauthenticated notes on Miden to settle transactions faster than the blocktime. Unauthenticated notes are essentially UTXOs that have not yet been fully committed into a block. This feature allows the notes to be created and consumed within the same block. + +We construct a chain of transactions using the unauthenticated notes method on the transaction builder. Unauthenticated notes are also referred to as "unauthenticated notes" or "erasable notes". We also demonstrate how a note can be serialized and deserialized, highlighting the ability to transfer notes between client instances for asset transfers that can be settled between parties faster than the blocktime. + +For example, our demo creates a chain of unauthenticated note transactions: + +```markdown +Alice ➡ Bob ➡ Charlie ➡ Dave ➡ Eve ➡ Frank ➡ ... +``` + +## What we'll cover + +- **Introduction to Unauthenticated Notes:** Understand what unauthenticated notes are and how they differ from standard notes. +- **Serialization Example:** See how to serialize and deserialize a note to demonstrate how notes can be propagated to client instances faster than the blocktime. +- **Performance Insights:** Observe how unauthenticated notes can reduce transaction times dramatically. + +## Step-by-step process + +1. **Client Initialization:** + - Set up an RPC client to connect with the Miden testnet. + - Initialize a random coin generator and a store for persisting account data. + +2. **Deploying a Fungible Faucet:** + - Use a random seed to deploy a fungible faucet. + - Configure the faucet parameters (symbol, decimals, and max supply) and add it to the client. + +3. **Creating Wallet Accounts:** + - Build multiple wallet accounts using a secure key generation process. + - Add these accounts to the client, making them ready for transactions. + +4. **Minting and Transacting with Unauthenticated Notes:** + - Mint tokens for one of the accounts (Alice) from the deployed faucet. + - Create a note representing the minted tokens. + - Build and submit a transaction that uses the unauthenticated note via the "unauthenticated" method. + - Serialize the note to demonstrate how it could be transferred to another client instance. + - Consume the note in a subsequent transaction, effectively creating a chain of unauthenticated transactions. + +5. **Performance Timing and Syncing:** + - Measure the time taken for each transaction iteration. + - Sync the client state and print account balances to verify the transactions. + +## Full Rust code example + +```rust +use miden_lib::account::auth::AuthRpoFalcon512; +use rand::{rngs::StdRng, RngCore}; +use std::sync::Arc; +use tokio::time::{sleep, Duration, Instant}; + +use miden_client::{ + account::component::{BasicFungibleFaucet, BasicWallet}, + address::NetworkId, + asset::{FungibleAsset, TokenSymbol}, + auth::AuthSecretKey, + builder::ClientBuilder, + keystore::FilesystemKeyStore, + note::{create_p2id_note, Note, NoteType}, + rpc::{Endpoint, GrpcClient}, + store::TransactionFilter, + transaction::{OutputNote, TransactionId, TransactionRequestBuilder, TransactionStatus}, + utils::{Deserializable, Serializable}, + Client, ClientError, Felt, +}; +use miden_client_sqlite_store::ClientBuilderSqliteExt; +use miden_objects::account::{AccountBuilder, AccountStorageMode, AccountType}; + +/// Waits for a specific transaction to be committed. +async fn wait_for_tx( + client: &mut Client>, + tx_id: TransactionId, +) -> Result<(), ClientError> { + loop { + client.sync_state().await?; + + // Check transaction status + let txs = client + .get_transactions(TransactionFilter::Ids(vec![tx_id])) + .await?; + let tx_committed = if !txs.is_empty() { + matches!(txs[0].status, TransactionStatus::Committed { .. }) + } else { + false + }; + + if tx_committed { + println!("✅ transaction {} committed", tx_id.to_hex()); + break; + } + + println!( + "Transaction {} not yet committed. Waiting...", + tx_id.to_hex() + ); + sleep(Duration::from_secs(2)).await; + } + Ok(()) +} + +#[tokio::main] +async fn main() -> Result<(), ClientError> { + // Initialize client + let endpoint = Endpoint::testnet(); + let timeout_ms = 10_000; + let rpc_client = Arc::new(GrpcClient::new(&endpoint, timeout_ms)); + + // Initialize keystore + let keystore_path = std::path::PathBuf::from("./keystore"); + let keystore = Arc::new(FilesystemKeyStore::::new(keystore_path).unwrap()); + + let store_path = std::path::PathBuf::from("./store.sqlite3"); + + let mut client = ClientBuilder::new() + .rpc(rpc_client) + .sqlite_store(store_path) + .authenticator(keystore.clone()) + .in_debug_mode(true.into()) + .build() + .await?; + + let sync_summary = client.sync_state().await.unwrap(); + println!("Latest block: {}", sync_summary.block_num); + + //------------------------------------------------------------ + // STEP 1: Deploy a fungible faucet + //------------------------------------------------------------ + println!("\n[STEP 1] Deploying a new fungible faucet."); + + // Faucet seed + let mut init_seed = [0_u8; 32]; + client.rng().fill_bytes(&mut init_seed); + + // Generate key pair + let key_pair = AuthSecretKey::new_rpo_falcon512(); + + // Faucet parameters + let symbol = TokenSymbol::new("MID").unwrap(); + let decimals = 8; + let max_supply = Felt::new(1_000_000); + + // Build the account + let faucet_account = AccountBuilder::new(init_seed) + .account_type(AccountType::FungibleFaucet) + .storage_mode(AccountStorageMode::Public) + .with_auth_component(AuthRpoFalcon512::new(key_pair.public_key().to_commitment())) + .with_component(BasicFungibleFaucet::new(symbol, decimals, max_supply).unwrap()) + .build() + .unwrap(); + + // Add the faucet to the client + client.add_account(&faucet_account, false).await?; + + println!( + "Faucet account ID: {}", + faucet_account.id().to_bech32(NetworkId::Testnet) + ); + + // Add the key pair to the keystore + keystore.add_key(&key_pair).unwrap(); + + // Resync to show newly deployed faucet + tokio::time::sleep(Duration::from_secs(2)).await; + client.sync_state().await?; + + //------------------------------------------------------------ + // STEP 2: Create basic wallet accounts + //------------------------------------------------------------ + println!("\n[STEP 2] Creating new accounts"); + + let mut accounts = vec![]; + let number_of_accounts = 10; + + for i in 0..number_of_accounts { + let mut init_seed = [0_u8; 32]; + client.rng().fill_bytes(&mut init_seed); + + let key_pair = AuthSecretKey::new_rpo_falcon512(); + + let account = AccountBuilder::new(init_seed) + .account_type(AccountType::RegularAccountUpdatableCode) + .storage_mode(AccountStorageMode::Public) + .with_auth_component(AuthRpoFalcon512::new(key_pair.public_key().to_commitment())) + .with_component(BasicWallet) + .build() + .unwrap(); + + accounts.push(account.clone()); + println!( + "account id {:?}: {}", + i, + account.id().to_bech32(NetworkId::Testnet) + ); + client.add_account(&account, true).await?; + + // Add the key pair to the keystore + keystore.add_key(&key_pair).unwrap(); + } + + // For demo purposes, Alice is the first account. + let alice = &accounts[0]; + + //------------------------------------------------------------ + // STEP 3: Mint and consume tokens for Alice + //------------------------------------------------------------ + println!("\n[STEP 3] Mint tokens"); + println!("Minting tokens for Alice..."); + let amount: u64 = 100; + let fungible_asset_mint_amount = FungibleAsset::new(faucet_account.id(), amount).unwrap(); + let transaction_request = TransactionRequestBuilder::new() + .build_mint_fungible_asset( + fungible_asset_mint_amount, + alice.id(), + NoteType::Public, + client.rng(), + ) + .unwrap(); + + let tx_id = client + .submit_new_transaction(faucet_account.id(), transaction_request) + .await?; + println!("Minted tokens. TX: {:?}", tx_id); + + // Wait for mint transaction to be committed + wait_for_tx(&mut client, tx_id).await?; + + // Get the minted note and consume it + let consumable_notes = client.get_consumable_notes(Some(alice.id())).await?; + + if let Some((note_record, _)) = consumable_notes.first() { + let transaction_request = TransactionRequestBuilder::new() + .build_consume_notes(vec![note_record.id()]) + .unwrap(); + + let consume_tx_id = client + .submit_new_transaction(alice.id(), transaction_request) + .await?; + println!("Consumed minted note. TX: {:?}", consume_tx_id); + + // Wait for consumption to complete + wait_for_tx(&mut client, consume_tx_id).await?; + } + + //------------------------------------------------------------ + // STEP 4: Create unauthenticated note tx chain + //------------------------------------------------------------ + println!("\n[STEP 4] Create unauthenticated note tx chain"); + let start = Instant::now(); + + for i in 0..number_of_accounts - 1 { + let loop_start = Instant::now(); + println!("\nunauthenticated tx {:?}", i + 1); + println!("sender: {}", accounts[i].id().to_bech32(NetworkId::Testnet)); + println!( + "target: {}", + accounts[i + 1].id().to_bech32(NetworkId::Testnet) + ); + + // Time the creation of the p2id note + let send_amount = 20; + let fungible_asset_send_amount = + FungibleAsset::new(faucet_account.id(), send_amount).unwrap(); + + // for demo purposes, unauthenticated notes can be public or private + let note_type = if i % 2 == 0 { + NoteType::Private + } else { + NoteType::Public + }; + + let p2id_note = create_p2id_note( + accounts[i].id(), + accounts[i + 1].id(), + vec![fungible_asset_send_amount.into()], + note_type, + Felt::new(0), + client.rng(), + ) + .unwrap(); + + let output_note = OutputNote::Full(p2id_note.clone()); + + // Time transaction request building + let transaction_request = TransactionRequestBuilder::new() + .own_output_notes(vec![output_note]) + .build() + .unwrap(); + + let tx_id = client + .submit_new_transaction(accounts[i].id(), transaction_request) + .await?; + println!("Created note. TX: {:?}", tx_id); + + // Note serialization/deserialization + // This demonstrates how you could send the serialized note to another client instance + let serialized = p2id_note.to_bytes(); + let deserialized_p2id_note = Note::read_from_bytes(&serialized).unwrap(); + + // Time consume note request building + let consume_note_request = TransactionRequestBuilder::new() + .unauthenticated_input_notes([(deserialized_p2id_note, None)]) + .build() + .unwrap(); + + let tx_id = client + .submit_new_transaction(accounts[i + 1].id(), consume_note_request) + .await?; + + println!( + "Consumed Note Tx on MidenScan: https://testnet.midenscan.com/tx/{:?}", + tx_id + ); + println!( + "Total time for loop iteration {}: {:?}", + i, + loop_start.elapsed() + ); + } + + println!( + "\nTotal execution time for unauthenticated note txs: {:?}", + start.elapsed() + ); + + // Final resync and display account balances + tokio::time::sleep(Duration::from_secs(3)).await; + client.sync_state().await?; + for account in accounts.clone() { + let new_account = client.get_account(account.id()).await.unwrap().unwrap(); + let balance = new_account + .account() + .vault() + .get_balance(faucet_account.id()) + .unwrap(); + println!( + "Account: {} balance: {}", + account.id().to_bech32(NetworkId::Testnet), + balance + ); + } + + Ok(()) +} +``` + +The output of our program will look something like this: + +```text +Latest block: 227040 + +[STEP 1] Deploying a new fungible faucet. +Faucet account ID: mtst1qqvhzywfzy4xugqqq0yqj28jxy3kr5hy + +[STEP 2] Creating new accounts +account id 0: mtst1qrdwf5hv6wnqzyqqq06hyfvqryn2nam3 +account id 1: mtst1qz7lwv4wh27xyyqqq026adcyc54ueccz +account id 2: mtst1qzzmpa7f3tcnkyqqqdgj4dan2q8r0s6c +account id 3: mtst1qrdclj0zp3v7qyqqqdn92ad87cl0rctl +account id 4: mtst1qre79420whvn2yqqq0udf4z8d5c3xwfj +account id 5: mtst1qpmfryrdjfwazyqqqdslm7gdhur80xhk +account id 6: mtst1qr0n4cxfddn2wyqqqv2vsc9mnqh0dtyj +account id 7: mtst1qrfmw4297mchwyqqqdfzq8dl2uu89uhq +account id 8: mtst1qpevlxpnuetesyqqqdwmsgd4zua84nda +account id 9: mtst1qre7lqnwt03zwyqqqvjdlj2w6yc87u4w + +[STEP 3] Mint tokens +Minting tokens for Alice... + +[STEP 4] Create unauthenticated note tx chain + +unauthenticated tx 1 +sender: mtst1qrdwf5hv6wnqzyqqq06hyfvqryn2nam3 +target: mtst1qz7lwv4wh27xyyqqq026adcyc54ueccz +Consumed Note Tx on MidenScan: https://testnet.midenscan.com/tx/0x31f48117c645c5b4ccff78ef356bad764798d4f207925e492ebbae1b86ef4f55 +Total time for loop iteration 0: 1.952243542s + +unauthenticated tx 2 +sender: mtst1qz7lwv4wh27xyyqqq026adcyc54ueccz +target: mtst1qzzmpa7f3tcnkyqqqdgj4dan2q8r0s6c +Consumed Note Tx on MidenScan: https://testnet.midenscan.com/tx/0x45b4c62c6e8e79a1c7200d1c84dc6304a88debd37b20b069dd739498827354c1 +Total time for loop iteration 1: 2.091625458s + +unauthenticated tx 3 +sender: mtst1qzzmpa7f3tcnkyqqqdgj4dan2q8r0s6c +target: mtst1qrdclj0zp3v7qyqqqdn92ad87cl0rctl +Consumed Note Tx on MidenScan: https://testnet.midenscan.com/tx/0xb2241e10df8f6f891b910975a3b4f4fd47657c47de164138300d683cfca5dd61 +Total time for loop iteration 2: 1.846021291s + +unauthenticated tx 4 +sender: mtst1qrdclj0zp3v7qyqqqdn92ad87cl0rctl +target: mtst1qre79420whvn2yqqq0udf4z8d5c3xwfj +Consumed Note Tx on MidenScan: https://testnet.midenscan.com/tx/0xd3ea6fa1da6c317f055ac4b069388d93b88d526039e01531879e75598e0f8cff +Total time for loop iteration 3: 1.877627958s + +unauthenticated tx 5 +sender: mtst1qre79420whvn2yqqq0udf4z8d5c3xwfj +target: mtst1qpmfryrdjfwazyqqqdslm7gdhur80xhk +Consumed Note Tx on MidenScan: https://testnet.midenscan.com/tx/0x6098638ec0ff7331432c037331ee7372977abe20af5c56315985fd314e21548d +Total time for loop iteration 4: 1.884586875s + +unauthenticated tx 6 +sender: mtst1qpmfryrdjfwazyqqqdslm7gdhur80xhk +target: mtst1qr0n4cxfddn2wyqqqv2vsc9mnqh0dtyj +Consumed Note Tx on MidenScan: https://testnet.midenscan.com/tx/0x8258292e49e0cfdd96603450c2de6738afecb1e7482ede0fb68ea375e884e1d8 +Total time for loop iteration 5: 1.886505875s + +unauthenticated tx 7 +sender: mtst1qr0n4cxfddn2wyqqqv2vsc9mnqh0dtyj +target: mtst1qrfmw4297mchwyqqqdfzq8dl2uu89uhq +Consumed Note Tx on MidenScan: https://testnet.midenscan.com/tx/0x9e0f84e00a9393bf6e5f224d55ccdf8bd0ef32ee20c3299e2dfccf1771001dfd +Total time for loop iteration 6: 2.095149458s + +unauthenticated tx 8 +sender: mtst1qrfmw4297mchwyqqqdfzq8dl2uu89uhq +target: mtst1qpevlxpnuetesyqqqdwmsgd4zua84nda +Consumed Note Tx on MidenScan: https://testnet.midenscan.com/tx/0xa9db6445dfaa44ccf9dd52bf4cd8d9057946571ccb5299a7a56c59faf2ed2093 +Total time for loop iteration 7: 1.935587291s + +unauthenticated tx 9 +sender: mtst1qpevlxpnuetesyqqqdwmsgd4zua84nda +target: mtst1qre7lqnwt03zwyqqqvjdlj2w6yc87u4w +Consumed Note Tx on MidenScan: https://testnet.midenscan.com/tx/0xba4bb4ae3c7aaf949cdd3be8c9ea52169f958e7dca8e9d4541fd5ac939393e41 +Total time for loop iteration 8: 1.964682833s + +Total execution time for unauthenticated note txs: 17.534611542s +blocks: [BlockNumber(227047), BlockNumber(227047), BlockNumber(227047), BlockNumber(227047), BlockNumber(227047), BlockNumber(227047), BlockNumber(227047), BlockNumber(227047), BlockNumber(227047)] +Account: mtst1qrdwf5hv6wnqzyqqq06hyfvqryn2nam3 balance: 80 +Account: mtst1qz7lwv4wh27xyyqqq026adcyc54ueccz balance: 0 +Account: mtst1qzzmpa7f3tcnkyqqqdgj4dan2q8r0s6c balance: 0 +Account: mtst1qrdclj0zp3v7qyqqqdn92ad87cl0rctl balance: 0 +Account: mtst1qre79420whvn2yqqq0udf4z8d5c3xwfj balance: 0 +Account: mtst1qpmfryrdjfwazyqqqdslm7gdhur80xhk balance: 0 +Account: mtst1qr0n4cxfddn2wyqqqv2vsc9mnqh0dtyj balance: 0 +Account: mtst1qrfmw4297mchwyqqqdfzq8dl2uu89uhq balance: 0 +Account: mtst1qpevlxpnuetesyqqqdwmsgd4zua84nda balance: 0 +Account: mtst1qre7lqnwt03zwyqqqvjdlj2w6yc87u4w balance: 20 +``` + +## Conclusion + +Unauthenticated notes on Miden offer a powerful mechanism for achieving faster asset settlements by allowing notes to be both created and consumed within the same block. In this guide, we walked through: + +- **Minting and Transacting with Unauthenticated Notes:** Building, serializing, and consuming notes quickly using the Miden client's "unauthenticated note" method. +- **Performance Observations:** Measuring and demonstrating how unauthenticated notes enable assets to be sent faster than the blocktime. + +By following this guide, you should now have a clear understanding of how to build and deploy high-performance transactions using unauthenticated notes on Miden. Unauthenticated notes are the ideal approach for applications like central limit order books (CLOBs) or other DeFi platforms where transaction speed is critical. + +### Running the example + +To run the unauthenticated note transfer example, navigate to the `rust-client` directory in the [miden-tutorials](https://github.com/0xMiden/miden-tutorials/) repository and run this command: + +```bash +cd rust-client +cargo run --release --bin unauthenticated_note_transfer +``` + +### Continue learning + +Next tutorial: [How to Use Mappings in Miden Assembly](mappings_in_masm_how_to.md) diff --git a/versioned_docs/version-0.12 (stable)/miden-tutorials/theme/Admonition/Icon/Danger.tsx b/versioned_docs/version-0.12 (stable)/miden-tutorials/theme/Admonition/Icon/Danger.tsx new file mode 100644 index 0000000..ab14d7d --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-tutorials/theme/Admonition/Icon/Danger.tsx @@ -0,0 +1,19 @@ +import React, { type ReactNode } from "react"; +import type { Props } from "@theme/Admonition/Icon/Danger"; + +export default function AdmonitionIconDanger(props: Props): ReactNode { + return ( + + + + ); +} diff --git a/versioned_docs/version-0.12 (stable)/miden-tutorials/theme/Admonition/Icon/Info.tsx b/versioned_docs/version-0.12 (stable)/miden-tutorials/theme/Admonition/Icon/Info.tsx new file mode 100644 index 0000000..59e48a5 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-tutorials/theme/Admonition/Icon/Info.tsx @@ -0,0 +1,20 @@ +import React, { type ReactNode } from "react"; +import type { Props } from "@theme/Admonition/Icon/Info"; + +export default function AdmonitionIconInfo(props: Props): ReactNode { + return ( + + + + ); +} diff --git a/versioned_docs/version-0.12 (stable)/miden-tutorials/theme/Admonition/Icon/Note.tsx b/versioned_docs/version-0.12 (stable)/miden-tutorials/theme/Admonition/Icon/Note.tsx new file mode 100644 index 0000000..d7c524b --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-tutorials/theme/Admonition/Icon/Note.tsx @@ -0,0 +1,20 @@ +import React, { type ReactNode } from "react"; +import type { Props } from "@theme/Admonition/Icon/Note"; + +export default function AdmonitionIconNote(props: Props): ReactNode { + return ( + + + + ); +} diff --git a/versioned_docs/version-0.12 (stable)/miden-tutorials/theme/Admonition/Icon/Tip.tsx b/versioned_docs/version-0.12 (stable)/miden-tutorials/theme/Admonition/Icon/Tip.tsx new file mode 100644 index 0000000..219bb8d --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-tutorials/theme/Admonition/Icon/Tip.tsx @@ -0,0 +1,20 @@ +import React, { type ReactNode } from "react"; +import type { Props } from "@theme/Admonition/Icon/Tip"; + +export default function AdmonitionIconTip(props: Props): ReactNode { + return ( + + + + ); +} diff --git a/versioned_docs/version-0.12 (stable)/miden-tutorials/theme/Admonition/Icon/Warning.tsx b/versioned_docs/version-0.12 (stable)/miden-tutorials/theme/Admonition/Icon/Warning.tsx new file mode 100644 index 0000000..f96398d --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-tutorials/theme/Admonition/Icon/Warning.tsx @@ -0,0 +1,20 @@ +import React, { type ReactNode } from "react"; +import type { Props } from "@theme/Admonition/Icon/Warning"; + +export default function AdmonitionIconCaution(props: Props): ReactNode { + return ( + + + + ); +} diff --git a/versioned_docs/version-0.12 (stable)/miden-tutorials/theme/Admonition/Layout/index.tsx b/versioned_docs/version-0.12 (stable)/miden-tutorials/theme/Admonition/Layout/index.tsx new file mode 100644 index 0000000..7b2c170 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-tutorials/theme/Admonition/Layout/index.tsx @@ -0,0 +1,51 @@ +import React, { type ReactNode } from "react"; +import clsx from "clsx"; +import { ThemeClassNames } from "@docusaurus/theme-common"; + +import type { Props } from "@theme/Admonition/Layout"; + +import styles from "./styles.module.css"; + +function AdmonitionContainer({ + type, + className, + children, +}: Pick & { children: ReactNode }) { + return ( +
+ {children} +
+ ); +} + +function AdmonitionHeading({ icon, title }: Pick) { + return ( +
+ {icon} + {/* {title} */} +
+ ); +} + +function AdmonitionContent({ children }: Pick) { + return children ? ( +
{children}
+ ) : null; +} + +export default function AdmonitionLayout(props: Props): ReactNode { + const { type, icon, title, children, className } = props; + return ( + + {title || icon ? : null} + {children} + + ); +} diff --git a/versioned_docs/version-0.12 (stable)/miden-tutorials/theme/Admonition/Layout/styles.module.css b/versioned_docs/version-0.12 (stable)/miden-tutorials/theme/Admonition/Layout/styles.module.css new file mode 100644 index 0000000..88df7e6 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-tutorials/theme/Admonition/Layout/styles.module.css @@ -0,0 +1,35 @@ +.admonition { + margin-bottom: 1em; +} + +.admonitionHeading { + font: var(--ifm-heading-font-weight) var(--ifm-h5-font-size) / + var(--ifm-heading-line-height) var(--ifm-heading-font-family); + text-transform: uppercase; +} + +/* Heading alone without content (does not handle fragment content) */ +.admonitionHeading:not(:last-child) { + margin-bottom: 0.3rem; +} + +.admonitionHeading code { + text-transform: none; +} + +.admonitionIcon { + display: inline-block; + vertical-align: middle; + margin-right: 0.4em; +} + +.admonitionIcon svg { + display: inline-block; + height: 1.6em; + width: 1.6em; + fill: var(--ifm-alert-foreground-color); +} + +.admonitionContent > :last-child { + margin-bottom: 0; +} diff --git a/versioned_docs/version-0.12 (stable)/miden-tutorials/theme/Admonition/Type/Caution.tsx b/versioned_docs/version-0.12 (stable)/miden-tutorials/theme/Admonition/Type/Caution.tsx new file mode 100644 index 0000000..b570a37 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-tutorials/theme/Admonition/Type/Caution.tsx @@ -0,0 +1,32 @@ +import React, {type ReactNode} from 'react'; +import clsx from 'clsx'; +import Translate from '@docusaurus/Translate'; +import type {Props} from '@theme/Admonition/Type/Caution'; +import AdmonitionLayout from '@theme/Admonition/Layout'; +import IconWarning from '@theme/Admonition/Icon/Warning'; + +const infimaClassName = 'alert alert--warning'; + +const defaultProps = { + icon: , + title: ( + + caution + + ), +}; + +// TODO remove before v4: Caution replaced by Warning +// see https://github.com/facebook/docusaurus/issues/7558 +export default function AdmonitionTypeCaution(props: Props): ReactNode { + return ( + + {props.children} + + ); +} diff --git a/versioned_docs/version-0.12 (stable)/miden-tutorials/theme/Admonition/Type/Danger.tsx b/versioned_docs/version-0.12 (stable)/miden-tutorials/theme/Admonition/Type/Danger.tsx new file mode 100644 index 0000000..49901fa --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-tutorials/theme/Admonition/Type/Danger.tsx @@ -0,0 +1,30 @@ +import React, {type ReactNode} from 'react'; +import clsx from 'clsx'; +import Translate from '@docusaurus/Translate'; +import type {Props} from '@theme/Admonition/Type/Danger'; +import AdmonitionLayout from '@theme/Admonition/Layout'; +import IconDanger from '@theme/Admonition/Icon/Danger'; + +const infimaClassName = 'alert alert--danger'; + +const defaultProps = { + icon: , + title: ( + + danger + + ), +}; + +export default function AdmonitionTypeDanger(props: Props): ReactNode { + return ( + + {props.children} + + ); +} diff --git a/versioned_docs/version-0.12 (stable)/miden-tutorials/theme/Admonition/Type/Info.tsx b/versioned_docs/version-0.12 (stable)/miden-tutorials/theme/Admonition/Type/Info.tsx new file mode 100644 index 0000000..018e0a1 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-tutorials/theme/Admonition/Type/Info.tsx @@ -0,0 +1,30 @@ +import React, {type ReactNode} from 'react'; +import clsx from 'clsx'; +import Translate from '@docusaurus/Translate'; +import type {Props} from '@theme/Admonition/Type/Info'; +import AdmonitionLayout from '@theme/Admonition/Layout'; +import IconInfo from '@theme/Admonition/Icon/Info'; + +const infimaClassName = 'alert alert--info'; + +const defaultProps = { + icon: , + title: ( + + info + + ), +}; + +export default function AdmonitionTypeInfo(props: Props): ReactNode { + return ( + + {props.children} + + ); +} diff --git a/versioned_docs/version-0.12 (stable)/miden-tutorials/theme/Admonition/Type/Note.tsx b/versioned_docs/version-0.12 (stable)/miden-tutorials/theme/Admonition/Type/Note.tsx new file mode 100644 index 0000000..c99e038 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-tutorials/theme/Admonition/Type/Note.tsx @@ -0,0 +1,30 @@ +import React, {type ReactNode} from 'react'; +import clsx from 'clsx'; +import Translate from '@docusaurus/Translate'; +import type {Props} from '@theme/Admonition/Type/Note'; +import AdmonitionLayout from '@theme/Admonition/Layout'; +import IconNote from '@theme/Admonition/Icon/Note'; + +const infimaClassName = 'alert alert--secondary'; + +const defaultProps = { + icon: , + title: ( + + note + + ), +}; + +export default function AdmonitionTypeNote(props: Props): ReactNode { + return ( + + {props.children} + + ); +} diff --git a/versioned_docs/version-0.12 (stable)/miden-tutorials/theme/Admonition/Type/Tip.tsx b/versioned_docs/version-0.12 (stable)/miden-tutorials/theme/Admonition/Type/Tip.tsx new file mode 100644 index 0000000..18604a5 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-tutorials/theme/Admonition/Type/Tip.tsx @@ -0,0 +1,30 @@ +import React, {type ReactNode} from 'react'; +import clsx from 'clsx'; +import Translate from '@docusaurus/Translate'; +import type {Props} from '@theme/Admonition/Type/Tip'; +import AdmonitionLayout from '@theme/Admonition/Layout'; +import IconTip from '@theme/Admonition/Icon/Tip'; + +const infimaClassName = 'alert alert--success'; + +const defaultProps = { + icon: , + title: ( + + tip + + ), +}; + +export default function AdmonitionTypeTip(props: Props): ReactNode { + return ( + + {props.children} + + ); +} diff --git a/versioned_docs/version-0.12 (stable)/miden-tutorials/theme/Admonition/Type/Warning.tsx b/versioned_docs/version-0.12 (stable)/miden-tutorials/theme/Admonition/Type/Warning.tsx new file mode 100644 index 0000000..61d9597 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-tutorials/theme/Admonition/Type/Warning.tsx @@ -0,0 +1,30 @@ +import React, {type ReactNode} from 'react'; +import clsx from 'clsx'; +import Translate from '@docusaurus/Translate'; +import type {Props} from '@theme/Admonition/Type/Warning'; +import AdmonitionLayout from '@theme/Admonition/Layout'; +import IconWarning from '@theme/Admonition/Icon/Warning'; + +const infimaClassName = 'alert alert--warning'; + +const defaultProps = { + icon: , + title: ( + + warning + + ), +}; + +export default function AdmonitionTypeWarning(props: Props): ReactNode { + return ( + + {props.children} + + ); +} diff --git a/versioned_docs/version-0.12 (stable)/miden-tutorials/theme/Admonition/Types.tsx b/versioned_docs/version-0.12 (stable)/miden-tutorials/theme/Admonition/Types.tsx new file mode 100644 index 0000000..2a10019 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-tutorials/theme/Admonition/Types.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import AdmonitionTypeNote from '@theme/Admonition/Type/Note'; +import AdmonitionTypeTip from '@theme/Admonition/Type/Tip'; +import AdmonitionTypeInfo from '@theme/Admonition/Type/Info'; +import AdmonitionTypeWarning from '@theme/Admonition/Type/Warning'; +import AdmonitionTypeDanger from '@theme/Admonition/Type/Danger'; +import AdmonitionTypeCaution from '@theme/Admonition/Type/Caution'; +import type AdmonitionTypes from '@theme/Admonition/Types'; + +const admonitionTypes: typeof AdmonitionTypes = { + note: AdmonitionTypeNote, + tip: AdmonitionTypeTip, + info: AdmonitionTypeInfo, + warning: AdmonitionTypeWarning, + danger: AdmonitionTypeDanger, +}; + +// Undocumented legacy admonition type aliases +// Provide hardcoded/untranslated retrocompatible label +// See also https://github.com/facebook/docusaurus/issues/7767 +const admonitionAliases: typeof AdmonitionTypes = { + secondary: (props) => , + important: (props) => , + success: (props) => , + caution: AdmonitionTypeCaution, +}; + +export default { + ...admonitionTypes, + ...admonitionAliases, +}; diff --git a/versioned_docs/version-0.12 (stable)/miden-tutorials/theme/Admonition/index.tsx b/versioned_docs/version-0.12 (stable)/miden-tutorials/theme/Admonition/index.tsx new file mode 100644 index 0000000..8f4225d --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-tutorials/theme/Admonition/index.tsx @@ -0,0 +1,21 @@ +import React, {type ComponentType, type ReactNode} from 'react'; +import {processAdmonitionProps} from '@docusaurus/theme-common'; +import type {Props} from '@theme/Admonition'; +import AdmonitionTypes from '@theme/Admonition/Types'; + +function getAdmonitionTypeComponent(type: string): ComponentType { + const component = AdmonitionTypes[type]; + if (component) { + return component; + } + console.warn( + `No admonition component found for admonition type "${type}". Using Info as fallback.`, + ); + return AdmonitionTypes.info!; +} + +export default function Admonition(unprocessedProps: Props): ReactNode { + const props = processAdmonitionProps(unprocessedProps); + const AdmonitionTypeComponent = getAdmonitionTypeComponent(props.type); + return ; +} diff --git a/versioned_docs/version-0.12 (stable)/miden-tutorials/web-client/.prettierrc b/versioned_docs/version-0.12 (stable)/miden-tutorials/web-client/.prettierrc new file mode 100644 index 0000000..665b6ea --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-tutorials/web-client/.prettierrc @@ -0,0 +1,9 @@ +{ + "printWidth": 80, + "tabWidth": 2, + "singleQuote": true, + "trailingComma": "all", + "proseWrap": "preserve", + "embeddedLanguageFormatting": "auto", + "endOfLine": "lf" +} diff --git a/versioned_docs/version-0.12 (stable)/miden-tutorials/web-client/_category_.yml b/versioned_docs/version-0.12 (stable)/miden-tutorials/web-client/_category_.yml new file mode 100644 index 0000000..783c286 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-tutorials/web-client/_category_.yml @@ -0,0 +1,4 @@ +label: "Web Client" +# Determines where this documentation section appears relative to other sections in the parent folder +position: 3 +collapsed: true diff --git a/versioned_docs/version-0.12 (stable)/miden-tutorials/web-client/counter_contract_tutorial.md b/versioned_docs/version-0.12 (stable)/miden-tutorials/web-client/counter_contract_tutorial.md new file mode 100644 index 0000000..67eef38 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-tutorials/web-client/counter_contract_tutorial.md @@ -0,0 +1,400 @@ +--- +title: 'Incrementing the Count of the Counter Contract' +sidebar_position: 5 +--- + +_Using the Miden WebClient to interact with a custom smart contract_ + +## Overview + +In this tutorial, we will interact with a counter contract already deployed on chain by incrementing the count using the Miden WebClient. + +Using a script, we will invoke the increment function within the counter contract to update the count. This tutorial provides a foundational understanding of interacting with custom smart contracts on Miden. + +## What we'll cover + +- Interacting with a custom smart contract on Miden +- Calling procedures in an account from a script + +## Prerequisites + +- Node `v20` or greater +- Familiarity with TypeScript +- `yarn` + +This tutorial assumes you have a basic understanding of Miden assembly. To quickly get up to speed with Miden assembly (MASM), please play around with running basic Miden assembly programs in the [Miden playground](https://0xmiden.github.io/examples/). + +## Step 1: Initialize your Next.js project + +1. Create a new Next.js app with TypeScript: + + ```bash + yarn create next-app@latest miden-web-app --typescript + ``` + + Hit enter for all terminal prompts. + +2. Change into the project directory: + + ```bash + cd miden-web-app + ``` + +3. Install the Miden WebClient SDK: + ```bash + yarn add @demox-labs/miden-sdk@0.12.3 + ``` + +**NOTE!**: Be sure to add the `--webpack` command to your `package.json` when running the `dev script`. The dev script should look like this: + +`package.json` + +```json + "scripts": { + "dev": "next dev --webpack", + ... + } +``` + +## Step 2: Edit the `app/page.tsx` file: + +Add the following code to the `app/page.tsx` file. This code defines the main page of our web application: + +```tsx +'use client'; +import { useState } from 'react'; +import { incrementCounterContract } from '../lib/incrementCounterContract'; + +export default function Home() { + const [isIncrementCounter, setIsIncrementCounter] = useState(false); + + const handleIncrementCounterContract = async () => { + setIsIncrementCounter(true); + await incrementCounterContract(); + setIsIncrementCounter(false); + }; + + return ( +
+
+

Miden Web App

+

Open your browser console to see WebClient logs.

+ +
+ +
+
+
+ ); +} +``` + +## Step 3 — Incrementing the Count of the Counter Contract + +Create the file `lib/incrementCounterContract.ts` and add the following code. + +``` +mkdir -p lib +touch lib/incrementCounterContract.ts +``` + +Copy and paste the following code into the `lib/incrementCounterContract.ts` file: + +```ts +// lib/incrementCounterContract.ts +export async function incrementCounterContract(): Promise { + if (typeof window === 'undefined') { + console.warn('webClient() can only run in the browser'); + return; + } + + // dynamic import → only in the browser, so WASM is loaded client‑side + const { + Address, + AccountBuilder, + AccountComponent, + AccountStorageMode, + AccountType, + SecretKey, + StorageMap, + StorageSlot, + TransactionRequestBuilder, + WebClient, + } = await import('@demox-labs/miden-sdk'); + + const nodeEndpoint = 'https://rpc.testnet.miden.io'; + const client = await WebClient.createClient(nodeEndpoint); + console.log('Current block number: ', (await client.syncState()).blockNum()); + + // Counter contract code in Miden Assembly + const counterContractCode = ` + use.miden::active_account + use.miden::native_account + use.std::sys + + const.COUNTER_SLOT=0 + + #! Inputs: [] + #! Outputs: [count] + export.get_count + push.COUNTER_SLOT + # => [index] + + exec.active_account::get_item + # => [count] + + # clean up stack + movdn.4 dropw + # => [count] + end + + #! Inputs: [] + #! Outputs: [] + export.increment_count + push.COUNTER_SLOT + # => [index] + + exec.active_account::get_item + # => [count] + + add.1 + # => [count+1] + + debug.stack + + push.COUNTER_SLOT + # [index, count+1] + + exec.native_account::set_item + # => [OLD_VALUE] + + dropw + # => [] + end +`; + + // Building the counter contract + // Counter contract account id on testnet + const counterContractId = Address.fromBech32( + 'mtst1arjemrxne8lj5qz4mg9c8mtyxg954483', + ).accountId(); + + // Reading the public state of the counter contract from testnet, + // and importing it into the WebClient + let counterContractAccount = await client.getAccount(counterContractId); + if (!counterContractAccount) { + await client.importAccountById(counterContractId); + await client.syncState(); + counterContractAccount = await client.getAccount(counterContractId); + if (!counterContractAccount) { + throw new Error(`Account not found after import: ${counterContractId}`); + } + } + + const builder = client.createScriptBuilder(); + const storageMap = new StorageMap(); + const storageSlotMap = StorageSlot.map(storageMap); + + const mappingAccountComponent = AccountComponent.compile( + counterContractCode, + builder, + [storageSlotMap], + ).withSupportsAllTypes(); + + const walletSeed = new Uint8Array(32); + crypto.getRandomValues(walletSeed); + + const secretKey = SecretKey.rpoFalconWithRNG(walletSeed); + const authComponent = AccountComponent.createAuthComponent(secretKey); + + const accountBuilderResult = new AccountBuilder(walletSeed) + .accountType(AccountType.RegularAccountImmutableCode) + .storageMode(AccountStorageMode.public()) + .withAuthComponent(authComponent) + .withComponent(mappingAccountComponent) + .build(); + + await client.addAccountSecretKeyToWebStore(secretKey); + await client.newAccount(accountBuilderResult.account, false); + + await client.syncState(); + + const accountCodeLib = builder.buildLibrary( + 'external_contract::counter_contract', + counterContractCode, + ); + + builder.linkDynamicLibrary(accountCodeLib); + + // Building the transaction script which will call the counter contract + const txScriptCode = ` +use.external_contract::counter_contract +begin +call.counter_contract::increment_count +end +`; + + const txScript = builder.compileTxScript(txScriptCode); + const txIncrementRequest = new TransactionRequestBuilder() + .withCustomScript(txScript) + .build(); + + // Executing the transaction script against the counter contract + await client.submitNewTransaction( + counterContractAccount.id(), + txIncrementRequest, + ); + + // Sync state + await client.syncState(); + + // Logging the count of counter contract + const counter = await client.getAccount(counterContractAccount.id()); + + // Here we get the first Word from storage of the counter contract + // A word is comprised of 4 Felts, 2**64 - 2**32 + 1 + const count = counter?.storage().getItem(0); + + // Converting the Word represented as a hex to a single integer value + const counterValue = Number( + BigInt('0x' + count!.toHex().slice(-16).match(/../g)!.reverse().join('')), + ); + + console.log('Count: ', counterValue); +} +``` + +To run the code above in our frontend, run the following command: + +``` +yarn dev +``` + +Open the browser console and click the button "Increment Counter Contract". + +This is what you should see in the browser console: + +``` +Current block number: 2168 +incrementCounterContract.ts:153 Count: 3 +``` + +## Miden Assembly Counter Contract Explainer + +#### Here's a breakdown of what the `get_count` procedure does: + +1. Pushes `0` (COUNTER_SLOT) onto the stack, representing the index of the storage slot to read. +2. Calls `account::get_item` with the index of `0`. +3. Calls `sys::truncate_stack` to truncate the stack to size 16. +4. The value returned from `account::get_item` is still on the stack and will be returned when this procedure is called. + +#### Here's a breakdown of what the `increment_count` procedure does: + +1. Pushes `0` (COUNTER_SLOT) onto the stack, representing the index of the storage slot to read. +2. Calls `account::get_item` with the index of `0`. +3. Pushes `1` onto the stack. +4. Adds `1` to the count value returned from `account::get_item`. +5. _For demonstration purposes_, calls `debug.stack` to see the state of the stack +6. Pushes `0` (COUNTER_SLOT) onto the stack, which is the index of the storage slot we want to write to. +7. Calls `account::set_item` which saves the incremented count to storage at index `0` +8. Calls `sys::truncate_stack` to truncate the stack to size 16. + +```masm +use.miden::active_account +use.miden::native_account +use.std::sys + +const.COUNTER_SLOT=0 + +#! Inputs: [] +#! Outputs: [count] +export.get_count + push.COUNTER_SLOT + # => [index] + + exec.active_account::get_item + # => [count] + + # clean up stack + movdn.4 dropw + # => [count] +end + +#! Inputs: [] +#! Outputs: [] +export.increment_count + push.COUNTER_SLOT + # => [index] + + exec.active_account::get_item + # => [count] + + add.1 + # => [count+1] + + debug.stack + + push.COUNTER_SLOT + # [index, count+1] + + exec.native_account::set_item + # => [OLD_VALUE] + + dropw + # => [] +end +``` + +**Note**: _It's a good habit to add comments below each line of MASM code with the expected stack state. This improves readability and helps with debugging._ + +### Authentication Component + +**Important**: All accounts must have an authentication component. For smart contracts that do not require authentication (like our counter contract), we use a `NoAuth` component. + +This `NoAuth` component allows any user to interact with the smart contract without requiring signature verification. + +**Note**: _Adding the `account::incr_nonce` to a state changing procedure allows any user to call the procedure._ + +### Custom script + +This is the Miden assembly script that calls the `increment_count` procedure during the transaction. + +```masm +use.external_contract::counter_contract + +begin + call.counter_contract::increment_count +end +``` + +### Running the example + +To run a full working example navigate to the `web-client` directory in the [miden-tutorials](https://github.com/0xMiden/miden-tutorials/) repository and run the web application example: + +```bash +cd web-client +yarn install +yarn start +``` + +### Resetting the `MidenClientDB` + +The Miden webclient stores account and note data in the browser. If you get errors such as "Failed to build MMR", then you should reset the Miden webclient store. When switching between Miden networks such as from localhost to testnet be sure to reset the browser store. To clear the account and node data in the browser, paste this code snippet into the browser console: + +```javascript +(async () => { + const dbs = await indexedDB.databases(); + for (const db of dbs) { + await indexedDB.deleteDatabase(db.name); + console.log(`Deleted database: ${db.name}`); + } + console.log('All databases deleted.'); +})(); +``` diff --git a/versioned_docs/version-0.12 (stable)/miden-tutorials/web-client/create_deploy_tutorial.md b/versioned_docs/version-0.12 (stable)/miden-tutorials/web-client/create_deploy_tutorial.md new file mode 100644 index 0000000..edc8a1a --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-tutorials/web-client/create_deploy_tutorial.md @@ -0,0 +1,307 @@ +--- +title: 'Creating Accounts and Deploying Faucets' +sidebar_position: 2 +--- + +_Using the Miden WebClient in TypeScript to create accounts and deploy faucets_ + +## Overview + +In this tutorial, we'll build a simple Next.js application that demonstrates the fundamentals of interacting with the Miden blockchain using the WebClient SDK. We'll walk through creating a Miden account for Alice and deploying a fungible faucet contract that can mint tokens. This sets the foundation for more complex operations like issuing assets and transferring them between accounts. + +## What we'll cover + +- Understanding the difference between public and private accounts & notes +- Instantiating the Miden client +- Creating new accounts (public or private) +- Deploying a faucet to fund an account + +## Prerequisites + +- Node `v20` or greater +- Familiarity with TypeScript +- `yarn` + +## Public vs. private accounts & notes + +Before we dive into code, a quick refresher: + +- **Public accounts**: The account's data and code are stored on-chain and are openly visible, including its assets. +- **Private accounts**: The account's state and logic are off-chain, only known to its owner. +- **Public notes**: The note's state is visible to anyone - perfect for scenarios where transparency is desired. +- **Private notes**: The note's state is stored off-chain, you will need to share the note data with the relevant parties (via email or Telegram) for them to be able to consume the note. + +> **Important**: In Miden, "accounts" and "smart contracts" can be used interchangeably due to native account abstraction. Every account is programmable and can contain custom logic. + +It is useful to think of notes on Miden as "cryptographic cashier's checks" that allow users to send tokens. If the note is private, the note transfer is only known to the sender and receiver. + +## Step 1: Initialize your Next.js project + +1. Create a new Next.js app with TypeScript: + + ```bash + yarn create next-app@latest miden-web-app --typescript + ``` + + Hit enter for all terminal prompts. + +2. Change into the project directory: + + ```bash + cd miden-web-app + ``` + +3. Install the Miden WebClient SDK: + ```bash + yarn add @demox-labs/miden-sdk@0.12.3 + ``` + +**NOTE!**: Be sure to add the `--webpack` command to your `package.json` when running the `dev script`. The dev script should look like this: + +`package.json` + +```json + "scripts": { + "dev": "next dev --webpack", + ... + } +``` + +## Step 2: Set up the WebClient + +The WebClient is your gateway to interact with the Miden blockchain. It handles state synchronization, transaction creation, and proof generation. Let's set it up. + +### Create `lib/createMintConsume.ts` + +First, we'll create a separate file for our blockchain logic. In the project root, create a folder `lib/` and inside it `createMintConsume.ts`: + +```bash +mkdir -p lib +touch lib/createMintConsume.ts +``` + +```ts +// lib/createMintConsume.ts +export async function createMintConsume(): Promise { + if (typeof window === 'undefined') { + console.warn('webClient() can only run in the browser'); + return; + } + + // dynamic import → only in the browser, so WASM is loaded client‑side + const { WebClient, AccountStorageMode, AccountId, NoteType } = + await import('@demox-labs/miden-sdk'); + + // Connect to Miden testnet RPC endpoint + const nodeEndpoint = 'https://rpc.testnet.miden.io'; + const client = await WebClient.createClient(nodeEndpoint); + + // 1. Sync with the latest blockchain state + // This fetches the latest block header and state commitments + const state = await client.syncState(); + console.log('Latest block number:', state.blockNum()); + + // At this point, your client is connected and synchronized + // Ready to create accounts and deploy contracts! +} +``` + +> Since we will be handling proof generation in the browser, it will be slower than proof generation handled by the Rust client. Check out the [tutorial on delegated proving](./creating_multiple_notes_tutorial.md#what-is-delegated-proving) to speed up proof generation in the browser. + +## Step 3: Create the User Interface + +Now let's create a simple UI that will trigger our blockchain interactions. We'll replace the default Next.js page with a button that calls our `createMintConsume()` function. + +Edit `app/page.tsx` to call `createMintConsume()` on a button click: + +```tsx +'use client'; +import { useState } from 'react'; +import { createMintConsume } from '../lib/createMintConsume'; + +export default function Home() { + const [isCreatingNotes, setIsCreatingNotes] = useState(false); + + const handleCreateMintConsume = async () => { + setIsCreatingNotes(true); + await createMintConsume(); + setIsCreatingNotes(false); + }; + + return ( +
+
+

Miden Web App

+

Open your browser console to see WebClient logs.

+ +
+ +
+
+
+ ); +} +``` + +## Step 4: Create Alice's Wallet Account + +Now we'll create Alice's account. Let's create a **public** account so we can easily track her transactions. + +Back in `lib/createMintConsume.ts`, extend the `createMintConsume()` function: + + +```ts +// lib/createMintConsume.ts +export async function createMintConsume(): Promise { + if (typeof window === 'undefined') { + console.warn('webClient() can only run in the browser'); + return; + } + + const { WebClient, AccountStorageMode } = await import( + "@demox-labs/miden-sdk" + ); + + const nodeEndpoint = 'https://rpc.testnet.miden.io'; + const client = await WebClient.createClient(nodeEndpoint); + + // 1. Sync with the latest blockchain state + const state = await client.syncState(); + console.log('Latest block number:', state.blockNum()); + + // 2. Create Alice's account + console.log('Creating account for Alice…'); + const alice = await client.newWallet( + AccountStorageMode.public(), // Public: account state is visible on-chain + true, // Mutable: account code can be upgraded later + 0 // Auth Scheme: 0 for RPO Falcon 512, 1 for ECDSA 256 Keccak + ); + console.log('Alice ID:', alice.id().toString()); +} +``` + + +## Step 5: Deploy a Fungible Faucet + +A faucet in Miden is a special type of account that can mint new tokens. Think of it as your own token factory. Let's deploy one that will create our custom "MID" tokens. + +Add this code after creating Alice's account: + + +```ts +// 3. Deploy a fungible faucet +// A faucet is an account that can mint new tokens +console.log('Creating faucet…'); +const faucetAccount = await client.newFaucet( + AccountStorageMode.public(), // Public: faucet operations are transparent + false, // Immutable: faucet rules cannot be changed + "MID", // Token symbol (like ETH, BTC, etc.) + 8, // Decimals (8 means 1 MID = 100,000,000 base units) + BigInt(1_000_000), // Max supply: total tokens that can ever be minted + 0 // Auth Scheme: 0 for RPO Falcon 512, 1 for ECDSA 256 Keccak +); +console.log('Faucet account ID:', faucetAccount.id().toString()); + +console.log('Setup complete.'); +``` + + +### Understanding Faucet Parameters: + +- **Storage Mode**: We use `public()` so anyone can verify the faucet's minting operations +- **Mutability**: Set to `false` to ensure the faucet rules can't be changed after deployment +- **Token Symbol**: A short identifier for your token (e.g., "MID", "USDC", "DAI") +- **Decimals**: Determines the smallest unit of your token. With 8 decimals, 1 MID = 10^8 base units +- **Max Supply**: The maximum number of tokens that can ever exist + +> **Note**: When tokens are minted from a faucet, they're created as "notes" - Miden's version of UTXOs. Each note contains tokens and can have specific spending conditions. + +## Summary + +In this tutorial, we've successfully: + +1. Set up a Next.js application with the Miden WebClient SDK +2. Connected to the Miden testnet +3. Created a wallet account for Alice +4. Deployed a fungible faucet that can mint custom tokens + +Your final `lib/createMintConsume.ts` should look like: + +```ts +// lib/createMintConsume.ts +export async function createMintConsume(): Promise { + if (typeof window === 'undefined') { + console.warn('webClient() can only run in the browser'); + return; + } + + // dynamic import → only in the browser, so WASM is loaded client‑side + const { WebClient, AccountStorageMode, NoteType, Address } = + await import('@demox-labs/miden-sdk'); + + const nodeEndpoint = 'https://rpc.testnet.miden.io'; + const client = await WebClient.createClient(nodeEndpoint); + + // 1. Sync with the latest blockchain state + const state = await client.syncState(); + console.log('Latest block number:', state.blockNum()); + + // 2. Create Alice's account + console.log('Creating account for Alice…'); + const alice = await client.newWallet(AccountStorageMode.public(), true, 0); + console.log('Alice ID:', alice.id().toString()); + + // 3. Deploy a fungible faucet + console.log('Creating faucet…'); + const faucet = await client.newFaucet( + AccountStorageMode.public(), + false, + 'MID', + 8, + BigInt(1_000_000), + 0, + ); + console.log('Faucet ID:', faucet.id().toString()); + + console.log('Setup complete.'); +} +``` + +### Running the example + +```bash +cd miden-web-app +yarn install +yarn dev +``` + +Open [http://localhost:3000](http://localhost:3000) in your browser, click **Tutorial #1: Create a wallet and deploy a faucet**, and check the browser console (F12 or right-click → Inspect → Console): + +``` +Latest block: 2247 +Creating account for Alice… +Alice ID: 0xd70b2072c6495d100000869a8bacf2 +Creating faucet… +Faucet ID: 0x2d7e506fb88dde200000a1386efec8 +Setup complete. +``` + +## What's Next? + +Now that you have: + +- A wallet account for Alice that can hold tokens +- A faucet that can mint new MID tokens + +In the next tutorial, we'll: + +1. Mint tokens from the faucet to Alice's account +2. Consume notes +3. Transfer tokens between accounts diff --git a/versioned_docs/version-0.12 (stable)/miden-tutorials/web-client/creating_multiple_notes_tutorial.md b/versioned_docs/version-0.12 (stable)/miden-tutorials/web-client/creating_multiple_notes_tutorial.md new file mode 100644 index 0000000..1668dc6 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-tutorials/web-client/creating_multiple_notes_tutorial.md @@ -0,0 +1,405 @@ +--- +title: 'Creating Multiple Notes in a Single Transaction' +sidebar_position: 4 +--- + +_Using the Miden WebClient in TypeScript to create several P2ID notes in a single transaction_ + +## Overview + +In the previous sections we learned how to create accounts, deploy faucets, and mint tokens. In this tutorial we will: + +- **Mint** test tokens from a faucet to Alice +- **Consume** the minted notes so the assets appear in Alice’s wallet +- **Create three P2ID notes in a _single_ transaction** using a custom note‑script and delegated proving + +The entire flow is wrapped in a helper called `multiSendWithDelegatedProver()` that you can call from any browser page. + +## What we’ll cover + +1. Setting‑up the WebClient and initializing a remote prover +2. Building three P2ID notes worth 100 `MID` each +3. Submitting the transaction _using delegated proving_ + +## Prerequisites + +- Node `v20` or greater +- Familiarity with TypeScript +- `yarn` + +## What is Delegated Proving? + +Before diving into our code example, let's clarify what in the world "delegated proving" actually is. + +Delegated proving is the process of outsourcing a part of the ZK proof generation of your transaction to a third party. For certain computationally constrained devices such as mobile phones and web browser environments, generating ZK proofs might take too long to ensure an acceptable user experience. Devices that do not have the computational resources to generate Miden proofs in under 1-2 seconds can use delegated proving to provide a more responsive user experience. + +_How does it work?_ When a user choses to use delegated proving, they send off a portion of the zk proof of their transaction to a dedicated server. This dedicated server generates the remainder of the ZK proof of the transaction and submits it to the network. Submitting a transaction with delegated proving is trustless, meaning if the delegated prover is malicious, the could not compromise the security of the account that is submitting a transaction to be processed by the delegated prover. The downside of using delegated proving is that it reduces the privacy of the account that uses delegated proving, because the delegated prover would have knowledge of the transaction that is being proven. Additionally, transactions that require sensitive data such as the knowledge of a hash preimage or a secret, should not use delegated proving as this data will be shared with the delegated prover for proof generation. + +Anyone can run their own delegated prover server. If you are building a product on Miden, it may make sense to run your own delegated prover server for your users. To run your own delegated proving server, follow the instructions here: https://crates.io/crates/miden-proving-service + +## Step 1: Initialize your Next.js project + +1. Create a new Next.js app with TypeScript: + + ```bash + yarn create next-app@latest miden-web-app --typescript + ``` + + Hit enter for all terminal prompts. + +2. Change into the project directory: + + ```bash + cd miden-web-app + ``` + +3. Install the Miden WebClient SDK: + ```bash + yarn add @demox-labs/miden-sdk@0.12.3 + ``` + +**NOTE!**: Be sure to add the `--webpack` command to your `package.json` when running the `dev script`. The dev script should look like this: + +`package.json` + +```json + "scripts": { + "dev": "next dev --webpack", + ... + } +``` + +## Step 2: Edit the `app/page.tsx` file: + +Add the following code to the `app/page.tsx` file: + +```tsx +'use client'; +import { useState } from 'react'; +import { multiSendWithDelegatedProver } from '../lib/multiSendWithDelegatedProver'; + +export default function Home() { + const [isMultiSendNotes, setIsMultiSendNotes] = useState(false); + + const handleMultiSendNotes = async () => { + setIsMultiSendNotes(true); + await multiSendWithDelegatedProver(); + setIsMultiSendNotes(false); + }; + + return ( +
+
+

Miden Web App

+

Open your browser console to see WebClient logs.

+ +
+ +
+
+
+ ); +} +``` + +## Step 3 — Initalize the WebClient + +Create the file `lib/multiSendWithDelegatedProver.ts` and add the following code. This snippet implements the function `multiSendWithDelegatedProver`, and initializes the WebClient along with the delegated prover endpoint. + +``` +mkdir -p lib +touch lib/multiSendWithDelegatedProver.ts +``` + +```ts +export async function multiSendWithDelegatedProver(): Promise { + // Ensure this runs only in a browser context + if (typeof window === 'undefined') return console.warn('Run in browser'); + + const { + WebClient, + AccountStorageMode, + AccountId, + NoteType, + TransactionProver, + Note, + NoteAssets, + OutputNoteArray, + Felt, + FungibleAsset, + TransactionRequestBuilder, + OutputNote, + } = await import('@demox-labs/miden-sdk'); + + const client = await WebClient.createClient('https://rpc.testnet.miden.io'); + const prover = TransactionProver.newRemoteProver( + 'https://tx-prover.testnet.miden.io', + ); + + console.log('Latest block:', (await client.syncState()).blockNum()); +} +``` + +## Step 4 — Create an account, deploy a faucet, mint and consume tokens  + +Add the code snippet below to the `multiSendWithDelegatedProver` function. This code creates a wallet and faucet, mints tokens from the faucet for the wallet, and then consumes the minted tokens. + +```ts +// ── Creating new account ────────────────────────────────────────────────────── +console.log('Creating account for Alice…'); +const alice = await client.newWallet(AccountStorageMode.public(), true, 0); +console.log('Alice accout ID:', alice.id().toString()); + +// ── Creating new faucet ────────────────────────────────────────────────────── +const faucet = await client.newFaucet( + AccountStorageMode.public(), + false, + 'MID', + 8, + BigInt(1_000_000), + 0, +); +console.log('Faucet ID:', faucet.id().toString()); + +// ── mint 10 000 MID to Alice ────────────────────────────────────────────────────── +{ + const txResult = await client.executeTransaction( + faucet.id(), + client.newMintTransactionRequest( + alice.id(), + faucet.id(), + NoteType.Public, + BigInt(10_000), + ), + ); + const proven = await client.proveTransaction(txResult, prover); + const submissionHeight = await client.submitProvenTransaction( + proven, + txResult, + ); + await client.applyTransaction(txResult, submissionHeight); + + console.log('waiting for settlement'); + await new Promise((r) => setTimeout(r, 7_000)); + await client.syncState(); +} + +// ── consume the freshly minted notes ────────────────────────────────────────────── +const noteIds = (await client.getConsumableNotes(alice.id())).map((rec) => + rec.inputNoteRecord().id().toString(), +); + +{ + const txResult = await client.executeTransaction( + alice.id(), + client.newConsumeTransactionRequest(noteIds), + ); + const proven = await client.proveTransaction(txResult, prover); + await client.syncState(); + const submissionHeight = await client.submitProvenTransaction( + proven, + txResult, + ); + await client.applyTransaction(txResult, submissionHeight); +} +``` + +## Step 5 — Build and Create P2ID notes + +Add the following code to the `multiSendWithDelegatedProver` function. This code defines three recipient addresses, builds three P2ID notes with 100 `MID` each, and then creates all three notes in the same transaction. + +```ts +// ── build 3 P2ID notes (100 MID each) ───────────────────────────────────────────── +const recipientAddresses = [ + '0xbf1db1694c83841000008cefd4fce0', + '0xee1a75244282c32000010a29bed5f4', + '0x67dc56bd0cbe629000006f36d81029', +]; + +const assets = new NoteAssets([new FungibleAsset(faucet.id(), BigInt(100))]); + +const p2idNotes = recipientAddresses.map((addr) => { + const receiverAccountId = AccountId.fromHex(addr); + const note = Note.createP2IDNote( + alice.id(), + receiverAccountId, + assets, + NoteType.Public, + new Felt(BigInt(0)), + ); + + return OutputNote.full(note); +}); + +// ── create all P2ID notes ─────────────────────────────────────────────────────────────── +await client.submitNewTransaction( + alice.id(), + new TransactionRequestBuilder() + .withOwnOutputNotes(new OutputNoteArray(p2idNotes)) + .build(), +); + +console.log('All notes created ✅'); +``` + +## Summary + +Your `lib/multiSendWithDelegatedProver.ts` file sould now look like this: + +```ts +/** + * Demonstrates multi-send functionality using a delegated prover on the Miden Network + * Creates multiple P2ID (Pay to ID) notes for different recipients + * + * @throws {Error} If the function cannot be executed in a browser environment + */ +export async function multiSendWithDelegatedProver(): Promise { + // Ensure this runs only in a browser context + if (typeof window === 'undefined') return console.warn('Run in browser'); + + const { + WebClient, + AccountStorageMode, + Address, + NoteType, + TransactionProver, + NetworkId, + Note, + NoteAssets, + OutputNoteArray, + Felt, + FungibleAsset, + TransactionRequestBuilder, + OutputNote, + } = await import('@demox-labs/miden-sdk'); + + const client = await WebClient.createClient('https://rpc.testnet.miden.io'); + const prover = TransactionProver.newRemoteProver( + 'https://tx-prover.testnet.miden.io', + ); + + console.log('Latest block:', (await client.syncState()).blockNum()); + + // ── Creating new account ────────────────────────────────────────────────────── + console.log('Creating account for Alice…'); + const alice = await client.newWallet(AccountStorageMode.public(), true, 0); + console.log('Alice accout ID:', alice.id().toString()); + + // ── Creating new faucet ────────────────────────────────────────────────────── + const faucet = await client.newFaucet( + AccountStorageMode.public(), + false, + 'MID', + 8, + BigInt(1_000_000), + 0, + ); + console.log('Faucet ID:', faucet.id().toString()); + + // ── mint 10 000 MID to Alice ────────────────────────────────────────────────────── + { + const txResult = await client.executeTransaction( + faucet.id(), + client.newMintTransactionRequest( + alice.id(), + faucet.id(), + NoteType.Public, + BigInt(10_000), + ), + ); + const proven = await client.proveTransaction(txResult, prover); + const submissionHeight = await client.submitProvenTransaction( + proven, + txResult, + ); + await client.applyTransaction(txResult, submissionHeight); + + console.log('waiting for settlement'); + await new Promise((r) => setTimeout(r, 7_000)); + await client.syncState(); + } + + // ── consume the freshly minted notes ────────────────────────────────────────────── + const noteIds = (await client.getConsumableNotes(alice.id())).map((rec) => + rec.inputNoteRecord().id().toString(), + ); + + { + const txResult = await client.executeTransaction( + alice.id(), + client.newConsumeTransactionRequest(noteIds), + ); + const proven = await client.proveTransaction(txResult, prover); + await client.syncState(); + const submissionHeight = await client.submitProvenTransaction( + proven, + txResult, + ); + await client.applyTransaction(txResult, submissionHeight); + } + + // ── build 3 P2ID notes (100 MID each) ───────────────────────────────────────────── + const recipientAddresses = [ + 'mtst1aqezqc90x7dkzypr9m5fmlpp85w6cl04', + 'mtst1apjg2ul76wrkxyr5qlcnczaskypa4ljn', + 'mtst1arpee6y9cm8t7ypn33pc8fzj6gkzz7kd', + ]; + + const assets = new NoteAssets([new FungibleAsset(faucet.id(), BigInt(100))]); + + const p2idNotes = recipientAddresses.map((addr) => { + const receiverAccountId = Address.fromBech32(addr).accountId(); + const note = Note.createP2IDNote( + alice.id(), + receiverAccountId, + assets, + NoteType.Public, + new Felt(BigInt(0)), + ); + + return OutputNote.full(note); + }); + + // ── create all P2ID notes ─────────────────────────────────────────────────────────────── + await client.submitNewTransaction( + alice.id(), + new TransactionRequestBuilder() + .withOwnOutputNotes(new OutputNoteArray(p2idNotes)) + .build(), + ); + + console.log('All notes created ✅'); +} +``` + +### Running the example + +To run a full working example navigate to the `web-client` directory in the [miden-tutorials](https://github.com/0xMiden/miden-tutorials/) repository and run the web application example: + +```bash +cd web-client +yarn install +yarn start +``` + +### Resetting the `MidenClientDB` + +The Miden webclient stores account and note data in the browser. To clear the account and node data in the browser, paste this code snippet into the browser console: + +```javascript +(async () => { + const dbs = await indexedDB.databases(); // Get all database names + for (const db of dbs) { + await indexedDB.deleteDatabase(db.name); + console.log(`Deleted database: ${db.name}`); + } + console.log('All databases deleted.'); +})(); +``` diff --git a/versioned_docs/version-0.12 (stable)/miden-tutorials/web-client/foreign_procedure_invocation_tutorial.md b/versioned_docs/version-0.12 (stable)/miden-tutorials/web-client/foreign_procedure_invocation_tutorial.md new file mode 100644 index 0000000..2b7db44 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-tutorials/web-client/foreign_procedure_invocation_tutorial.md @@ -0,0 +1,599 @@ +--- +title: 'Foreign Procedure Invocation' +sidebar_position: 7 +--- + +# Foreign Procedure Invocation Tutorial + +_Using foreign procedure invocation to craft read-only cross-contract calls with the WebClient_ + +## Overview + +In previous tutorials we deployed a public counter contract and incremented the count from a different client instance. + +In this tutorial we will cover the basics of "foreign procedure invocation" (FPI) using the WebClient. To demonstrate FPI, we will build a "count copy" smart contract that reads the count from our previously deployed counter contract and copies the count to its own local storage. + +Foreign procedure invocation (FPI) is a powerful tool for building composable smart contracts in Miden. FPI allows one smart contract or note to read the state of another contract. + +The term "foreign procedure invocation" might sound a bit verbose, but it is as simple as one smart contract calling a non-state modifying procedure in another smart contract. The "EVM equivalent" of foreign procedure invocation would be a smart contract calling a read-only function in another contract. + +FPI is useful for developing smart contracts that extend the functionality of existing contracts on Miden. FPI is the core primitive used by price oracles on Miden. + +## What We Will Build + +![Count Copy FPI diagram](../img/count_copy_fpi_diagram.png) + +The diagram above depicts the "count copy" smart contract using foreign procedure invocation to read the count state of the counter contract. After reading the state via FPI, the "count copy" smart contract writes the value returned from the counter contract to storage. + +## What we'll cover + +- Foreign Procedure Invocation (FPI) with the WebClient +- Building a "count copy" smart contract +- Executing cross-contract calls in the browser + +## Prerequisites + +- Node `v20` or greater +- Familiarity with TypeScript +- `yarn` + +This tutorial assumes you have a basic understanding of Miden assembly and completed the previous tutorial on incrementing the counter contract. To quickly get up to speed with Miden assembly (MASM), please play around with running basic Miden assembly programs in the [Miden playground](https://0xmiden.github.io/examples/). + +## Step 1: Initialize your Next.js project + +1. Create a new Next.js app with TypeScript: + + ```bash + yarn create next-app@latest miden-fpi-app --typescript + ``` + + Hit enter for all terminal prompts. + +2. Change into the project directory: + + ```bash + cd miden-fpi-app + ``` + +3. Install the Miden WebClient SDK: + ```bash + yarn install @demox-labs/miden-sdk@0.12.3 + ``` + +**NOTE!**: Be sure to add the `--webpack` command to your `package.json` when running the `dev script`. The dev script should look like this: + +`package.json` + +```json + "scripts": { + "dev": "next dev --webpack", + ... + } +``` + +## Step 2: Edit the `app/page.tsx` file + +Add the following code to the `app/page.tsx` file. This code defines the main page of our web application: + +```tsx +'use client'; +import { useState } from 'react'; +import { foreignProcedureInvocation } from '../lib/foreignProcedureInvocation'; + +export default function Home() { + const [isFPIRunning, setIsFPIRunning] = useState(false); + + const handleForeignProcedureInvocation = async () => { + setIsFPIRunning(true); + await foreignProcedureInvocation(); + setIsFPIRunning(false); + }; + + return ( +
+
+

Miden FPI Web App

+

Open your browser console to see WebClient logs.

+ +
+ +
+
+
+ ); +} +``` + +## Step 3: Create the Foreign Procedure Invocation Implementation + +Create the file `lib/foreignProcedureInvocation.ts` and add the following code. + +```bash +mkdir -p lib +touch lib/foreignProcedureInvocation.ts +``` + +Copy and paste the following code into the `lib/foreignProcedureInvocation.ts` file: + +```ts +// lib/foreignProcedureInvocation.ts +export async function foreignProcedureInvocation(): Promise { + if (typeof window === 'undefined') { + console.warn('foreignProcedureInvocation() can only run in the browser'); + return; + } + + // dynamic import → only in the browser, so WASM is loaded client‑side + const { + AccountBuilder, + AccountComponent, + Address, + AccountType, + MidenArrays, + SecretKey, + StorageSlot, + TransactionRequestBuilder, + ForeignAccount, + AccountStorageRequirements, + WebClient, + AccountStorageMode, + } = await import('@demox-labs/miden-sdk'); + + const nodeEndpoint = 'https://rpc.testnet.miden.io'; + const client = await WebClient.createClient(nodeEndpoint); + console.log('Current block number: ', (await client.syncState()).blockNum()); + + // ------------------------------------------------------------------------- + // STEP 1: Create the Count Reader Contract + // ------------------------------------------------------------------------- + console.log('\n[STEP 1] Creating count reader contract.'); + + // Count reader contract code in Miden Assembly (exactly from count_reader.masm) + const countReaderCode = ` + use.miden::active_account + use.miden::native_account + use.miden::tx + use.std::sys + + # => [account_id_prefix, account_id_suffix, get_count_proc_hash] + export.copy_count + exec.tx::execute_foreign_procedure + # => [count] + + push.0 + # [index, count] + + debug.stack + + exec.native_account::set_item dropw + # => [] + + exec.sys::truncate_stack + # => [] + end +`; + + const builder = client.createScriptBuilder(); + const countReaderComponent = AccountComponent.compile( + countReaderCode, + builder, + [StorageSlot.emptyValue()], + ).withSupportsAllTypes(); + + const walletSeed = new Uint8Array(32); + crypto.getRandomValues(walletSeed); + + const secretKey = SecretKey.rpoFalconWithRNG(walletSeed); + const authComponent = AccountComponent.createAuthComponent(secretKey); + + const countReaderContract = new AccountBuilder(walletSeed) + .accountType(AccountType.RegularAccountImmutableCode) + .storageMode(AccountStorageMode.public()) + .withAuthComponent(authComponent) + .withComponent(countReaderComponent) + .build(); + + await client.addAccountSecretKeyToWebStore(secretKey); + await client.syncState(); + + // Create the count reader contract account (using available WebClient API) + console.log('Creating count reader contract account...'); + console.log( + 'Count reader contract ID:', + countReaderContract.account.id().toString(), + ); + + await client.newAccount(countReaderContract.account, false); + + // ------------------------------------------------------------------------- + // STEP 2: Build & Get State of the Counter Contract + // ------------------------------------------------------------------------- + console.log('\n[STEP 2] Building counter contract from public state'); + + // Define the Counter Contract account id from counter contract deploy (same as Rust) + const counterContractId = Address.fromBech32( + 'mtst1arjemrxne8lj5qz4mg9c8mtyxg954483', + ).accountId(); + + // Import the counter contract + let counterContractAccount = await client.getAccount(counterContractId); + if (!counterContractAccount) { + await client.importAccountById(counterContractId); + await client.syncState(); + counterContractAccount = await client.getAccount(counterContractId); + if (!counterContractAccount) { + throw new Error(`Account not found after import: ${counterContractId}`); + } + } + console.log( + 'Account storage slot 0:', + counterContractAccount.storage().getItem(0)?.toHex(), + ); + + // ------------------------------------------------------------------------- + // STEP 3: Call the Counter Contract via Foreign Procedure Invocation (FPI) + // ------------------------------------------------------------------------- + console.log( + '\n[STEP 3] Call counter contract with FPI from count reader contract', + ); + + // Counter contract code (exactly from counter.masm) + const counterContractCode = ` + use.miden::active_account + use.miden::native_account + use.std::sys + + const.COUNTER_SLOT=0 + + #! Inputs: [] + #! Outputs: [count] + export.get_count + push.COUNTER_SLOT + # => [index] + + exec.active_account::get_item + # => [count] + + # clean up stack + movdn.4 dropw + # => [count] + end + + #! Inputs: [] + #! Outputs: [] + export.increment_count + push.COUNTER_SLOT + # => [index] + + exec.active_account::get_item + # => [count] + + add.1 + # => [count+1] + + debug.stack + + push.COUNTER_SLOT + # [index, count+1] + + exec.native_account::set_item + # => [OLD_VALUE] + + dropw + # => [] + end +`; + + // Create the counter contract component to get the procedure hash (following Rust pattern) + const counterContractComponent = AccountComponent.compile( + counterContractCode, + builder, + [StorageSlot.emptyValue()], + ).withSupportsAllTypes(); + + const getCountProcHash = + counterContractComponent.getProcedureHash('get_count'); + + // Build the script that calls the count reader contract (exactly from reader_script.masm with replacements) + const fpiScriptCode = ` + use.external_contract::count_reader_contract + use.std::sys + + begin + push.${getCountProcHash} + # => [GET_COUNT_HASH] + + push.${counterContractAccount.id().suffix()} + # => [account_id_suffix, GET_COUNT_HASH] + + push.${counterContractAccount.id().prefix()} + # => [account_id_prefix, account_id_suffix, GET_COUNT_HASH] + + call.count_reader_contract::copy_count + # => [] + + exec.sys::truncate_stack + # => [] + + end +`; + + // Create the library for the count reader contract + const countReaderLib = builder.buildLibrary( + 'external_contract::count_reader_contract', + countReaderCode, + ); + builder.linkDynamicLibrary(countReaderLib); + + // Compile the transaction script with the count reader library + const txScript = builder.compileTxScript(fpiScriptCode); + + // foreign account + const storageRequirements = new AccountStorageRequirements(); + const foreignAccount = ForeignAccount.public( + counterContractId, + storageRequirements, + ); + + // Build a transaction request with the custom script + const txRequest = new TransactionRequestBuilder() + .withCustomScript(txScript) + .withForeignAccounts(new MidenArrays.ForeignAccountArray([foreignAccount])) + .build(); + + // Execute the transaction on the count reader contract and send it to the network (following Rust pattern) + const txResult = await client.submitNewTransaction( + countReaderContract.account.id(), + txRequest, + ); + + console.log( + 'View transaction on MidenScan: https://testnet.midenscan.com/tx/' + + txResult.toHex(), + ); + + await client.syncState(); + + // Retrieve updated contract data to see the results (following Rust pattern) + const updatedCounterContract = await client.getAccount( + counterContractAccount.id(), + ); + console.log( + 'counter contract storage:', + updatedCounterContract?.storage().getItem(0)?.toHex(), + ); + + const updatedCountReaderContract = await client.getAccount( + countReaderContract.account.id(), + ); + console.log( + 'count reader contract storage:', + updatedCountReaderContract?.storage().getItem(0)?.toHex(), + ); + + // Log the count value copied via FPI + const countReaderStorage = updatedCountReaderContract?.storage().getItem(0); + if (countReaderStorage) { + const countValue = Number( + BigInt( + '0x' + + countReaderStorage + .toHex() + .slice(-16) + .match(/../g)! + .reverse() + .join(''), + ), + ); + console.log('Count copied via Foreign Procedure Invocation:', countValue); + } + + console.log('\nForeign Procedure Invocation Transaction completed!'); +} +``` + +To run the code above in our frontend, run the following command: + +```bash +yarn dev +``` + +Open the browser console and click the button "Foreign Procedure Invocation Tutorial". + +This is what you should see in the browser console: + +``` +Current block number: 2168 + +[STEP 1] Creating count reader contract. +Count reader contract ID: 0x90128b4e27f34500000720bedaa49b + +[STEP 2] Building counter contract from public state +Account storage slot 0: 0x0000000000000000000000000000000000000000000000001200000000000000 + +[STEP 3] Call counter contract with FPI from count reader contract +fpiScript + use.external_contract::count_reader_contract + use.std::sys + + begin + push.0x92495ca54d519eb5e4ba22350f837904d3895e48d74d8079450f19574bb84cb6 + # => [GET_COUNT_HASH] + + push.297741160627968 + # => [account_id_suffix, GET_COUNT_HASH] + + push.12911083037950619392 + # => [account_id_prefix, account_id_suffix, GET_COUNT_HASH] + + call.count_reader_contract::copy_count + # => [] + + exec.sys::truncate_stack + # => [] + + end +View transaction on MidenScan: https://testnet.midenscan.com/tx/0xffff3dc5454154d1ccf64c1ad170bdef2df471c714f6fe6ab542d060396b559f +counter contract storage: 0x0000000000000000000000000000000000000000000000001200000000000000 +count reader contract storage: 0x0000000000000000000000000000000000000000000000001200000000000000 +Count copied via Foreign Procedure Invocation: 18 + +Foreign Procedure Invocation Transaction completed! +``` + +## Understanding the Count Reader Contract + +The count reader smart contract contains a `copy_count` procedure that uses `tx::execute_foreign_procedure` to call the `get_count` procedure in the counter contract. + +```masm +use.miden::active_account +use.miden::native_account +use.miden::tx +use.std::sys + +# => [account_id_prefix, account_id_suffix, get_count_proc_hash] +export.copy_count + exec.tx::execute_foreign_procedure + # => [count] + + push.0 + # [index, count] + + debug.stack + + exec.native_account::set_item dropw + # => [] + + exec.sys::truncate_stack + # => [] +end +``` + +To call the `get_count` procedure, we push its hash along with the counter contract's ID suffix and prefix onto the stack before calling `tx::execute_foreign_procedure`. + +The stack state before calling `tx::execute_foreign_procedure` should look like this: + +``` +# => [account_id_prefix, account_id_suffix, GET_COUNT_HASH] +``` + +After calling the `get_count` procedure in the counter contract, we save the count of the counter contract to index 0 in storage. + +## Understanding the Transaction Script + +The transaction script that executes the foreign procedure invocation looks like this: + +```masm +use.external_contract::count_reader_contract +use.std::sys + +begin + push.${getCountProcHash} + # => [GET_COUNT_HASH] + + push.${counterContractAccount.id().suffix()} + # => [account_id_suffix, GET_COUNT_HASH] + + push.${counterContractAccount.id().prefix()} + # => [account_id_prefix, account_id_suffix, GET_COUNT_HASH] + + call.count_reader_contract::copy_count + # => [] + + exec.sys::truncate_stack + # => [] +end +``` + +This script: + +1. Pushes the procedure hash of the `get_count` function +2. Pushes the counter contract's account ID suffix and prefix +3. Calls the `copy_count` procedure in our count reader contract +4. Truncates the stack + +## Key WebClient Concepts for FPI + +### Getting Procedure Hashes + +In the WebClient, we get the procedure hash using the [`getProcedureHash`](https://github.com/0xMiden/miden-tutorials/blob/7bfa1996979cbb221b8cab455596093535787784/web-client/lib/foreignProcedureInvocation.ts#L176) method: + +```ts +let getCountProcHash = counterContractComponent.getProcedureHash('get_count'); +``` + +### Foreign Accounts + +To execute foreign procedure calls, we need to specify the foreign account in our transaction request: + +```ts +let foreignAccount = ForeignAccount.public( + counterContractId, + storageRequirements, +); + +let txRequest = new TransactionRequestBuilder() + .withCustomScript(txScript) + .withForeignAccounts(new MidenArrays.ForeignAccountArray([foreignAccount])) + .build(); +``` + +### Account Component Libraries + +We create a library for the count reader contract so our transaction script can call its procedures: + +```ts +const countReaderLib = builder.buildLibrary( + 'external_contract::count_reader_contract', + countReaderCode, +); +builder.linkDynamicLibrary(countReaderLib); +``` + +## Summary + +In this tutorial we created a smart contract that calls the `get_count` procedure in the counter contract using foreign procedure invocation, and then saves the returned value to its local storage using the Miden WebClient. + +The key steps were: + +1. Creating a count reader contract with a `copy_count` procedure +2. Importing the counter contract from the network +3. Getting the procedure hash for the `get_count` function +4. Building a transaction script that calls our count reader contract +5. Executing the transaction with a foreign account reference + +### Running the example + +To run a full working example navigate to the `web-client` directory in the [miden-tutorials](https://github.com/0xMiden/miden-tutorials/) repository and run the web application example: + +```bash +cd web-client +yarn install +yarn start +``` + +### Resetting the `MidenClientDB` + +The Miden webclient stores account and note data in the browser. If you get errors such as "Failed to build MMR", then you should reset the Miden webclient store. When switching between Miden networks such as from localhost to testnet be sure to reset the browser store. To clear the account and node data in the browser, paste this code snippet into the browser console: + +```javascript +(async () => { + const dbs = await indexedDB.databases(); + for (const db of dbs) { + await indexedDB.deleteDatabase(db.name); + console.log(`Deleted database: ${db.name}`); + } + console.log('All databases deleted.'); +})(); +``` + +### Continue learning + +Next tutorial: [Creating Multiple Notes](creating_multiple_notes_tutorial.md) diff --git a/versioned_docs/version-0.12 (stable)/miden-tutorials/web-client/index.md b/versioned_docs/version-0.12 (stable)/miden-tutorials/web-client/index.md new file mode 100644 index 0000000..7389df5 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-tutorials/web-client/index.md @@ -0,0 +1,17 @@ +--- +title: 'Web Client' +sidebar_position: 1 +--- + +TypeScript library, which can be used to programmatically interact with the Miden rollup. + +The Miden WebClient can be used for a variety of things, including: + +- Deploying and creating transactions to interact with accounts and notes on Miden. +- Storing the state of accounts and notes in the browser. +- Generating and submitting proofs of transactions. +- Submitting transactions to delegated proving services. + +This section of the docs is an overview of the different things one can achieve using the WebClient, and how to implement them. + +Keep in mind that both the WebClient and the documentation are works-in-progress! diff --git a/versioned_docs/version-0.12 (stable)/miden-tutorials/web-client/mint_consume_create_tutorial.md b/versioned_docs/version-0.12 (stable)/miden-tutorials/web-client/mint_consume_create_tutorial.md new file mode 100644 index 0000000..7d60365 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-tutorials/web-client/mint_consume_create_tutorial.md @@ -0,0 +1,282 @@ +--- +title: 'Mint, Consume, and Create Notes' +sidebar_position: 3 +--- + +_Using the Miden WebClient in TypeScript to mint, consume, and transfer assets_ + +## Overview + +In the previous tutorial, we set up the foundation - creating Alice's wallet and deploying a faucet. Now we'll put these to use by minting and transferring assets. + +## What we'll cover + +- Minting assets from a faucet +- Consuming notes to fund an account +- Sending tokens to other users + +## Prerequisites + +This tutorial builds directly on the previous one. Make sure you have: + +- Completed the "Creating Accounts and Deploying Faucets" tutorial +- Your Next.js app with the Miden WebClient set up + +## Understanding Notes in Miden + +Before we start coding, it's important to understand **notes**: + +- Minting a note from a faucet does not automatically add the tokens to your account balance. It creates a note addressed to you. +- You must **consume** a note to add its tokens to your account balance. +- Until consumed, tokens exist in the note but aren't in your account yet. + +## Step 1: Mint tokens from the faucet + +Let's mint some tokens for Alice. When we mint from a faucet, it creates a note containing the specified amount of tokens targeted to Alice's account. + +Add this to the end of your `createMintConsume` function in `lib/createMintConsume.ts`: + + + +```ts +// 4. Mint tokens from the faucet to Alice +await client.syncState(); + +console.log("Minting tokens to Alice..."); +const mintTxRequest = client.newMintTransactionRequest( + alice.id(), // Target account (who receives the tokens) + faucet.id(), // Faucet account (who mints the tokens) + NoteType.Public, // Note visibility (public = onchain) + BigInt(1000), // Amount to mint (in base units) +); + +await client.submitNewTransaction(faucet.id(), mintTxRequest); + +// Wait for the transaction to be processed +console.log("Waiting 10 seconds for transaction confirmation..."); +await new Promise((resolve) => setTimeout(resolve, 10000)); +await client.syncState(); +``` + + + +### What's happening here? + +1. **newMintTransactionRequest**: Creates a request to mint tokens to Alice. Note that this is only possible to submit transactions on the faucets' behalf if the user controls the faucet (i.e. its keys are stored in the client). +2. **newTransaction**: Locally executes and proves the transaction. +3. **submitTransaction**: Sends the transaction to the network. +4. Wait 10 seconds for the transaction to be included in a block. + +## Step 2: Find consumable notes + +After minting, Alice has a note waiting for her but the tokens aren't in her account yet. +To identify notes that are ready to consume, the Miden WebClient provides the `getConsumableNotes` function: + +```ts +// 5. Find notes available for consumption +const consumableNotes = await client.getConsumableNotes(alice.id()); +console.log(`Found ${consumableNotes.length} note(s) to consume`); + +const noteIds = consumableNotes.map((note) => + note.inputNoteRecord().id().toString(), +); +console.log('Consumable note IDs:', noteIds); +``` + +## Step 3: Consume notes in a single transaction + +Now let's consume the notes to add the tokens to Alice's account balance: + +```ts +// 6. Consume the notes to add tokens to Alice's balance +console.log('Consuming minted notes...'); +const consumeTxRequest = client.newConsumeTransactionRequest(mintedNoteIds); + +await client.submitNewTransaction(alice.id(), consumeTxRequest); + +await client.syncState(); +console.log('Notes consumed.'); +``` + +## Step 4: Sending tokens to other accounts + +After consuming the notes, Alice has tokens in her wallet. Now, she wants to send tokens to her friends. She has two options: create a separate transaction for each transfer or batch multiple notes in a single transaction. + +_The standard asset transfer note on Miden is the P2ID note (Pay-to-Id). There is also the P2IDE (Pay-to-Id Extended) variant which allows for both timelocking the note (target can only spend the note after a certain block height) and for the note to be reclaimable (the creator of the note can reclaim the note after a certain block height)._ + +Now that Alice has tokens in her account, she can send some to Bob: + + +```ts +// Add this import at the top of the file +import { NoteType } from "@demox-labs/miden-sdk"; +// ... + +// 7. Send tokens from Alice to Bob +const bobAccountId = Address.fromBech32( + 'mtst1apve54rq8ux0jqqqqrkh5y0r0y8cwza6_qruqqypuyph', +).accountId(); +console.log("Sending tokens to Bob's account..."); + +const sendTxRequest = client.newSendTransactionRequest( + alice.id(), // Sender account ID + bobAccountId, // Recipient account ID + faucet.id(), // Asset ID (faucet that created the tokens) + NoteType.Public, // Note visibility + BigInt(100), // Amount to send +); + +await client.submitNewTransaction(alice.id(), sendTxRequest); + +console.log('Tokens sent successfully!'); +``` + + + +### Understanding P2ID notes + +The transaction creates a **P2ID (Pay-to-ID)** note: + +- It's the standard way to transfer assets in Miden +- The note is "locked" to Bob's account ID, i.e. only Bob can consume this note to receive the tokens +- Public notes are visible onchain; private notes would need to be shared offchain (e.g. via a private channel) + +## Summary + +Here's the complete `lib/createMintConsume.ts` file: + +```ts +// lib/createMintConsume.ts +export async function createMintConsume(): Promise { + if (typeof window === 'undefined') { + console.warn('webClient() can only run in the browser'); + return; + } + + // dynamic import → only in the browser, so WASM is loaded client‑side + const { WebClient, AccountStorageMode, NoteType, Address } = + await import('@demox-labs/miden-sdk'); + + const nodeEndpoint = 'https://rpc.testnet.miden.io'; + const client = await WebClient.createClient(nodeEndpoint); + + // 1. Sync with the latest blockchain state + const state = await client.syncState(); + console.log('Latest block number:', state.blockNum()); + + // 2. Create Alice's account + console.log('Creating account for Alice…'); + const alice = await client.newWallet(AccountStorageMode.public(), true, 0); + console.log('Alice ID:', alice.id().toString()); + + // 3. Deploy a fungible faucet + console.log('Creating faucet…'); + const faucet = await client.newFaucet( + AccountStorageMode.public(), + false, + 'MID', + 8, + BigInt(1_000_000), + 0, + ); + console.log('Faucet ID:', faucet.id().toString()); + + await client.syncState(); + + // 4. Mint tokens to Alice + await client.syncState(); + + console.log('Minting tokens to Alice...'); + const mintTxRequest = client.newMintTransactionRequest( + alice.id(), + faucet.id(), + NoteType.Public, + BigInt(1000), + ); + + await client.submitNewTransaction(faucet.id(), mintTxRequest); + + console.log('Waiting 10 seconds for transaction confirmation...'); + await new Promise((resolve) => setTimeout(resolve, 10000)); + await client.syncState(); + + // 5. Fetch minted notes + const mintedNotes = await client.getConsumableNotes(alice.id()); + const mintedNoteIds = mintedNotes.map((n) => + n.inputNoteRecord().id().toString(), + ); + console.log('Minted note IDs:', mintedNoteIds); + + // 6. Consume minted notes + console.log('Consuming minted notes...'); + const consumeTxRequest = client.newConsumeTransactionRequest(mintedNoteIds); + + await client.submitNewTransaction(alice.id(), consumeTxRequest); + + await client.syncState(); + console.log('Notes consumed.'); + + // 7. Send tokens to Bob + const bobAccountId = Address.fromBech32( + 'mtst1apve54rq8ux0jqqqqrkh5y0r0y8cwza6_qruqqypuyph', + ).accountId(); + console.log("Sending tokens to Bob's account..."); + const sendTxRequest = client.newSendTransactionRequest( + alice.id(), + bobAccountId, + faucet.id(), + NoteType.Public, + BigInt(100), + ); + + await client.submitNewTransaction(alice.id(), sendTxRequest); + console.log('Tokens sent successfully!'); +} +``` + +Let's run the `lib/createMintConsume.ts` function again. Reload the page and click "Start WebClient". + +The output will look like this: + +``` +Latest block number: 4807 +Creating account for Alice... +Alice ID: 0x1a20f4d1321e681000005020e69b1a +Creating faucet... +Faucet ID: 0xaa86a6f05ae40b2000000f26054d5d +Minting 1000 tokens to Alice... +Waiting 10 seconds for transaction confirmation... +Consumable note IDs: ['0x4edbb3d5dbdf694...'] +Consuming notes... +Notes consumed. +Sending tokens to Bob's account... +Tokens sent successfully! +``` + +### Resetting the `MidenClientDB` + +The Miden webclient stores account and note data in the browser. To clear the account and note data in the browser, paste this code snippet into the browser console: + +```javascript +(async () => { + const dbs = await indexedDB.databases(); // Get all database names + for (const db of dbs) { + await indexedDB.deleteDatabase(db.name); + console.log(`Deleted database: ${db.name}`); + } + console.log('All databases deleted.'); +})(); +``` + +## What's next? + +You've now learned the complete note lifecycle in Miden: + +1. **Minting** - Creating new tokens from a faucet (issued in notes) +2. **Consuming** - Adding tokens from notes to an account +3. **Transferring** - Sending tokens to other accounts + +In the next tutorials, we'll explore: + +- Creating multiple notes in a single transaction +- Delegated proving diff --git a/versioned_docs/version-0.12 (stable)/miden-tutorials/web-client/unauthenticated_note_how_to.md b/versioned_docs/version-0.12 (stable)/miden-tutorials/web-client/unauthenticated_note_how_to.md new file mode 100644 index 0000000..62ad92f --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-tutorials/web-client/unauthenticated_note_how_to.md @@ -0,0 +1,447 @@ +--- +title: 'How to Use Unauthenticated Notes' +sidebar_position: 6 +--- + +_Using unauthenticated notes for optimistic note consumption with the Miden WebClient_ + +## Overview + +In this tutorial, we will explore how to leverage unauthenticated notes on Miden to settle transactions faster than the blocktime using the Miden WebClient. Unauthenticated notes are essentially UTXOs that have not yet been fully committed into a block. This feature allows the notes to be created and consumed within the same batch during [batch production](https://0xmiden.github.io/miden-docs/imported/miden-base/src/blockchain.html#batch-production). + +When using unauthenticated notes, both the creation and consumption of notes can happen within the same batch, enabling faster-than-blocktime settlement. This is particularly powerful for applications requiring high-frequency transactions or optimistic settlement patterns. + +We construct a chain of transactions using the unauthenticated notes method on the transaction builder. Unauthenticated notes are also referred to as "erasable notes". We also demonstrate how a note can be created and consumed, highlighting the ability to transfer notes between client instances for asset transfers that can be settled between parties faster than the blocktime. + +For example, our demo creates a chain of unauthenticated note transactions: + +```markdown +Alice ➡ Wallet 1 ➡ Wallet 2 ➡ Wallet 3 ➡ Wallet 4 ➡ Wallet 5 +``` + +## What we'll cover + +- **Introduction to Unauthenticated Notes:** Understand what unauthenticated notes are and how they differ from standard notes. +- **WebClient Setup:** Configure the Miden WebClient for browser-based transactions. +- **P2ID Note Creation:** Learn how to create Pay-to-ID notes for targeted transfers. +- **Performance Insights:** Observe how unauthenticated notes can reduce transaction times dramatically. + +## Prerequisites + +- Node `v20` or greater +- Familiarity with TypeScript +- `yarn` + +This tutorial assumes you have a basic understanding of Miden assembly. To quickly get up to speed with Miden assembly (MASM), please play around with running basic Miden assembly programs in the [Miden playground](https://0xmiden.github.io/examples/). + +## Step-by-step process + +1. **Next.js Project Setup:** + - Create a new Next.js application with TypeScript. + - Install the Miden WebClient SDK. + +2. **WebClient Initialization:** + - Set up the WebClient to connect with the Miden testnet. + - Configure a delegated prover for improved performance. + +3. **Account Creation:** + - Create wallet accounts for Alice and multiple transfer recipients. + - Deploy a fungible faucet for token minting. + +4. **Initial Token Setup:** + - Mint tokens from the faucet to Alice's account. + - Consume the minted tokens to prepare for transfers. + +5. **Unauthenticated Note Transfer Chain:** + - Create P2ID (Pay-to-ID) notes for each transfer in the chain. + - Use unauthenticated input notes to consume notes faster than blocktime. + - Measure and observe the performance benefits. + +## Step 1: Initialize your Next.js project + +1. Create a new Next.js app with TypeScript: + + ```bash + yarn create next-app@latest miden-web-app --typescript + ``` + + Hit enter for all terminal prompts. + +2. Change into the project directory: + + ```bash + cd miden-web-app + ``` + +3. Install the Miden WebClient SDK: + ```bash + yarn add @demox-labs/miden-sdk@0.12.3 + ``` + +**NOTE!**: Be sure to add the `--webpack` command to your `package.json` when running the `dev script`. The dev script should look like this: + +`package.json` + +```json + "scripts": { + "dev": "next dev --webpack", + ... + } +``` + +## Step 2: Edit the `app/page.tsx` file + +Add the following code to the `app/page.tsx` file. This code defines the main page of our web application: + +```tsx +'use client'; +import { useState } from 'react'; +import { unauthenticatedNoteTransfer } from '../lib/unauthenticatedNoteTransfer'; + +export default function Home() { + const [isTransferring, setIsTransferring] = useState(false); + + const handleUnauthenticatedNoteTransfer = async () => { + setIsTransferring(true); + await unauthenticatedNoteTransfer(); + setIsTransferring(false); + }; + + return ( +
+
+

Miden Web App

+

Open your browser console to see WebClient logs.

+ +
+ +
+
+
+ ); +} +``` + +## Step 3: Create the Unauthenticated Note Transfer Implementation + +Create the file `lib/unauthenticatedNoteTransfer.ts` and add the following code: + +```bash +mkdir -p lib +touch lib/unauthenticatedNoteTransfer.ts +``` + +Copy and paste the following code into the `lib/unauthenticatedNoteTransfer.ts` file: + +```ts +/** + * Demonstrates unauthenticated note transfer chain using a delegated prover on the Miden Network + * Creates a chain of P2ID (Pay to ID) notes: Alice → wallet 1 → wallet 2 → wallet 3 → wallet 4 + * + * @throws {Error} If the function cannot be executed in a browser environment + */ +export async function unauthenticatedNoteTransfer(): Promise { + // Ensure this runs only in a browser context + if (typeof window === 'undefined') return console.warn('Run in browser'); + + const { + WebClient, + AccountStorageMode, + NoteType, + TransactionProver, + Note, + NoteAssets, + OutputNoteArray, + Felt, + FungibleAsset, + NoteAndArgsArray, + NoteAndArgs, + TransactionRequestBuilder, + OutputNote, + } = await import('@demox-labs/miden-sdk'); + + const client = await WebClient.createClient('https://rpc.testnet.miden.io'); + const prover = TransactionProver.newRemoteProver( + 'https://tx-prover.testnet.miden.io', + ); + + console.log('Latest block:', (await client.syncState()).blockNum()); + + // ── Creating new account ────────────────────────────────────────────────────── + console.log('Creating accounts'); + + console.log('Creating account for Alice…'); + const alice = await client.newWallet(AccountStorageMode.public(), true, 0); + console.log('Alice accout ID:', alice.id().toString()); + + const wallets = []; + for (let i = 0; i < 5; i++) { + const wallet = await client.newWallet(AccountStorageMode.public(), true, 0); + wallets.push(wallet); + console.log('wallet ', i.toString(), wallet.id().toString()); + } + + // ── Creating new faucet ────────────────────────────────────────────────────── + const faucet = await client.newFaucet( + AccountStorageMode.public(), + false, + 'MID', + 8, + BigInt(1_000_000), + 0, + ); + console.log('Faucet ID:', faucet.id().toString()); + + // ── mint 10 000 MID to Alice ────────────────────────────────────────────────────── + { + const txResult = await client.executeTransaction( + faucet.id(), + client.newMintTransactionRequest( + alice.id(), + faucet.id(), + NoteType.Public, + BigInt(10_000), + ), + ); + const proven = await client.proveTransaction(txResult, prover); + const submissionHeight = await client.submitProvenTransaction( + proven, + txResult, + ); + await client.applyTransaction(txResult, submissionHeight); + } + + console.log('Waiting for settlement'); + await new Promise((r) => setTimeout(r, 7_000)); + await client.syncState(); + + // ── Consume the freshly minted note ────────────────────────────────────────────── + const noteIds = (await client.getConsumableNotes(alice.id())).map((rec) => + rec.inputNoteRecord().id().toString(), + ); + + { + const txResult = await client.executeTransaction( + alice.id(), + client.newConsumeTransactionRequest(noteIds), + ); + const proven = await client.proveTransaction(txResult, prover); + const submissionHeight = await client.submitProvenTransaction( + proven, + txResult, + ); + await client.applyTransaction(txResult, submissionHeight); + await client.syncState(); + } + + // ── Create unauthenticated note transfer chain ───────────────────────────────────────────── + // Alice → wallet 1 → wallet 2 → wallet 3 → wallet 4 + for (let i = 0; i < wallets.length; i++) { + console.log(`\nUnauthenticated tx ${i + 1}`); + + // Determine sender and receiver for this iteration + const sender = i === 0 ? alice : wallets[i - 1]; + const receiver = wallets[i]; + + console.log('Sender:', sender.id().toString()); + console.log('Receiver:', receiver.id().toString()); + + const assets = new NoteAssets([new FungibleAsset(faucet.id(), BigInt(50))]); + const p2idNote = Note.createP2IDNote( + sender.id(), + receiver.id(), + assets, + NoteType.Public, + new Felt(BigInt(0)), // aux value + ); + + const outputP2ID = OutputNote.full(p2idNote); + + console.log('Creating P2ID note...'); + { + const txResult = await client.executeTransaction( + sender.id(), + new TransactionRequestBuilder() + .withOwnOutputNotes(new OutputNoteArray([outputP2ID])) + .build(), + ); + const proven = await client.proveTransaction(txResult, prover); + const submissionHeight = await client.submitProvenTransaction( + proven, + txResult, + ); + await client.applyTransaction(txResult, submissionHeight); + } + + console.log('Consuming P2ID note...'); + + const noteIdAndArgs = new NoteAndArgs(p2idNote, null); + + const consumeRequest = new TransactionRequestBuilder() + .withUnauthenticatedInputNotes(new NoteAndArgsArray([noteIdAndArgs])) + .build(); + + { + const txResult = await client.executeTransaction( + receiver.id(), + consumeRequest, + ); + const proven = await client.proveTransaction(txResult, prover); + const submissionHeight = await client.submitProvenTransaction( + proven, + txResult, + ); + const txExecutionResult = await client.applyTransaction( + txResult, + submissionHeight, + ); + + const txId = txExecutionResult + .executedTransaction() + .id() + .toHex() + .toString(); + + console.log( + `Consumed Note Tx on MidenScan: https://testnet.midenscan.com/tx/${txId}`, + ); + } + } + + console.log('Asset transfer chain completed ✅'); +} +``` + +## Key Concepts: Unauthenticated Notes + +### What are Unauthenticated Notes? + +Unauthenticated notes are a powerful feature that allows notes to be: + +- **Created and consumed in the same block** +- **Transferred faster than blocktime** +- **Used for optimistic transactions** + +### Performance Benefits + +By using unauthenticated notes, we can: + +- Skip waiting for block confirmation between note creation and consumption +- Create transaction chains that execute within a single block +- Achieve sub-blocktime settlement for certain use cases + +### Use Cases + +Unauthenticated notes are ideal for: + +- **High-frequency trading applications** +- **Payment channels** +- **Micropayment systems** +- **Any scenario requiring fast settlement** + +## Running the Example + +To run the unauthenticated note transfer example: + +```bash +cd miden-web-app +yarn install +yarn dev +``` + +Open [http://localhost:3000](http://localhost:3000) in your browser, click the **"Tutorial #4: Unauthenticated Note Transfer"** button, and check the browser console for detailed logs. + +### Expected Output + +You should see output similar to this in the browser console: + +``` +🚀 Starting unauthenticated note transfer demo +Latest block: 2247 + +[STEP 1] Creating wallet accounts +Creating account for Alice… +Alice account ID: 0xd70b2072c6495d100000869a8bacf2 +Wallet 1 ID: 0x2d7e506fb88dde200000a1386efec8 +Wallet 2 ID: 0x1a8c3f4e2b9d5a600000c7e9b2f4d8 +... + +[STEP 2] Deploying a fungible faucet +Faucet ID: 0x8f2a1b7c3e5d9f800000d4a6c8e2b5 + +[STEP 3] Minting tokens to Alice +Waiting for settlement... + +[STEP 4] Consuming minted tokens + +[STEP 5] Creating unauthenticated note transfer chain +Transfer chain: Alice → Wallet 1 → Wallet 2 → Wallet 3 → Wallet 4 → Wallet 5 + +--- Unauthenticated transfer 1 --- +Sender: 0xd70b2072c6495d100000869a8bacf2 +Receiver: 0x2d7e506fb88dde200000a1386efec8 +Creating P2ID note... +Consuming P2ID note with unauthenticated input... +✅ Consumed Note Tx on MidenScan: https://testnet.midenscan.com/tx/0x1234... +⏱️ Iteration 1 completed in: 2341ms + +... + +🏁 Total execution time for unauthenticated note transfers: 11847ms +✅ Asset transfer chain completed successfully! + +[FINAL BALANCES] +Alice balance: 9750 MID +Wallet 1 balance: 0 MID +Wallet 2 balance: 0 MID +Wallet 3 balance: 0 MID +Wallet 4 balance: 0 MID +Wallet 5 balance: 50 MID +``` + +## Conclusion + +Unauthenticated notes on Miden offer a powerful mechanism for achieving faster asset settlements by allowing notes to be both created and consumed within the same block. In this guide, we walked through: + +- **Setting up the Miden WebClient** with delegated proving for optimal performance +- **Creating P2ID Notes** for targeted asset transfers between specific accounts +- **Building Transaction Chains** using unauthenticated input notes for sub-blocktime settlement +- **Performance Observations** demonstrating how unauthenticated notes enable faster-than-blocktime transfers + +By following this guide, you should now have a clear understanding of how to build and deploy high-performance transactions using unauthenticated notes on Miden with the WebClient. Unauthenticated notes are the ideal approach for applications like central limit order books (CLOBs) or other DeFi platforms where transaction speed is critical. + +### Resetting the `MidenClientDB` + +The Miden webclient stores account and note data in the browser. If you get errors such as "Failed to build MMR", then you should reset the Miden webclient store. When switching between Miden networks such as from localhost to testnet be sure to reset the browser store. To clear the account and node data in the browser, paste this code snippet into the browser console: + +```javascript +(async () => { + const dbs = await indexedDB.databases(); + for (const db of dbs) { + await indexedDB.deleteDatabase(db.name); + console.log(`Deleted database: ${db.name}`); + } + console.log('All databases deleted.'); +})(); +``` + +### Running the Full Example + +To run a full working example navigate to the `web-client` directory in the [miden-tutorials](https://github.com/0xMiden/miden-tutorials/) repository and run the web application example: + +```bash +cd web-client +yarn install +yarn start +``` + +### Continue learning + +Next tutorial: [Creating Multiple Notes](creating_multiple_notes_tutorial.md) diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/_category_.yml b/versioned_docs/version-0.12 (stable)/miden-vm/_category_.yml new file mode 100644 index 0000000..ea52768 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-vm/_category_.yml @@ -0,0 +1,4 @@ +label: "Virtual Machine" +# Determines where this documentation section appears relative to other sections on the main documentation page (which is the parent of this folder in the miden-docs repository) +position: 7 +collapsed: true diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/advanced_topics/_category_.yml b/versioned_docs/version-0.12 (stable)/miden-vm/advanced_topics/_category_.yml new file mode 100644 index 0000000..23cf4ba --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-vm/advanced_topics/_category_.yml @@ -0,0 +1,4 @@ +label: "Advanced Topics" +# Determines where this documentation section appears relative to other sections in the parent folder +position: 9 +collapsed: true diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/advanced_topics/execution_trace_optimization.md b/versioned_docs/version-0.12 (stable)/miden-vm/advanced_topics/execution_trace_optimization.md new file mode 100644 index 0000000..c8d0b38 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-vm/advanced_topics/execution_trace_optimization.md @@ -0,0 +1,58 @@ +--- +title: "Execution Trace Optimization" +sidebar_position: 2 +draft: true +--- + +# Execution trace optimization + +## Understanding cycle counts in Miden VM + +When we refer to "number of cycles" in most Miden VM documentation, we're specifically referring to the **stack rows** portion of the execution trace. However, the actual proving time is determined by what we call the "true number of cycles," which is the maximum of all trace segment lengths: + +- **Stack rows**: One row per VM operation (what `clk` outputs). This corresponds to the System, Program decoder and Operand Stack set of columns from the [execution trace diagram](../design/index.md#execution-trace) +- **Range checker rows**: Added for all u32 and memory operations (no more, no less) +- **Chiplet rows**: Added when opcodes call specialized chiplets: + - `hperm` calls the hasher chiplet + - `and`, `or` (and other bitwise ops) call the bitwise chiplet + - memory operations call the memory chiplet + - syscalls call the kernel ROM chiplet + +The **true number of rows in the final trace** is precisely `max(stack_rows, range_checker_rows, chiplet_rows)` + +Note: The maximum gets rounded up to the next power of 2, and the other 2 sets of columns get padded up to this maximum. + +In some cases, either the range checker or chiplets could end up requiring more rows than the stack rows, making the true cycle count higher than what the stack-based cycle counter reports. + +## Analyzing trace segments with miden-vm analyze + +The `miden-vm analyze` command provides detailed information about trace segment utilization, showing: +- Stack rows used +- Range checker rows used +- Chiplet rows used +- The resulting true number of cycles (maximum of the three) + +This tool helps identify which trace segments are driving the ultimate trace length, and hence overall proving time for a given program. + +## Trace segment growth and proving performance + +Even when two programs run the same number of VM cycles, their proving time can differ significantly because of how the execution trace is structured. + +| Trace segment | Purpose | Native growth rule | +| ------------------ | ---------------------------------------------------- | ------------------------------------------------------ | +| Stack rows | Core transition constraints; one row per opcode | +1 row for every operation | +| Range‑checker rows | Ensure selected values lie in \[0 .. 2¹⁶) | Rows added for all u32 and memory operations | +| Chiplet rows | Bitwise, hash, memory and other accelerator circuits | Rows added only when an opcode calls a chiplet | + +1. **Independent growth** + Each segment expands on its own. A pure arithmetic loop inflates only the stack segment, whereas repeated hashing inflates the chiplet segment. + +2. **Power‑of‑two padding** + After execution halts, the prover finds the largest trace segment length `L`, rounds it up to the next power of two `Lʹ = 2^ceil(log₂ L)`, and pads all segments until all trace segments reach `Lʹ`. The prover expects a square trace matrix of this size. + +> Padding doesn't simply mean "filling with zeros." Instead, padding means setting the cells to whatever values make the constraints work. While this can be intuitively thought of as "setting the cells to 0" in many cases, the actual padding values are determined by what satisfies the AIR constraints for each specific trace segment. + +3. **Cost driver** + Proving time grows roughly with `Lʹ`, not with the raw cycle count. Programs that rely heavily on chiplets might have the same cycle count but significantly longer proving times due to trace segment growth. Opcodes that touch only the stack keep every segment short, yielding faster proofs. Opcodes that generate many chiplet or range‑checker rows can push `L` past a power‑of‑two boundary, doubling every segment's length after padding and markedly increasing proving time. Mixing opcode types unevenly can thus produce a cycle‑efficient program that is still proving‑expensive. + +**Take‑away**: track which segment each opcode stresses, batch chiplet‑heavy work, and watch the next power‑of‑two boundary; staying below it can nearly halve proof time. diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/advanced_topics/index.md b/versioned_docs/version-0.12 (stable)/miden-vm/advanced_topics/index.md new file mode 100644 index 0000000..0dd6006 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-vm/advanced_topics/index.md @@ -0,0 +1,9 @@ +--- +title: "Advanced Topics" +sidebar_position: 1 +draft: true +--- + +# Advanced Topics + +This section covers advanced concepts and implementation details for users who want to understand the deeper aspects of Miden VM. \ No newline at end of file diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/background.md b/versioned_docs/version-0.12 (stable)/miden-vm/background.md new file mode 100644 index 0000000..3f559a9 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-vm/background.md @@ -0,0 +1,36 @@ +--- +title: "Background Material" +sidebar_position: 8 +--- + +# Background Material + +Proofs of execution generated by Miden VM are based on STARKs. A STARK is a novel proof-of-computation scheme that allows you to create an efficiently verifiable proof that a computation was executed correctly. The scheme was developed by Eli Ben-Sasson, Michael Riabzev et al. at Technion - Israel Institute of Technology. STARKs do not require an initial trusted setup, and rely on very few cryptographic assumptions. + +Here are some resources to learn more about STARKs: + +- STARKs paper: [Scalable, transparent, and post-quantum secure computational integrity](https://eprint.iacr.org/2018/046) +- STARKs vs. SNARKs: [A Cambrian Explosion of Crypto Proofs](https://nakamoto.com/cambrian-explosion-of-crypto-proofs/) + +Vitalik Buterin's blog series on zk-STARKs: + +- [STARKs, part 1: Proofs with Polynomials](https://vitalik.eth.limo/general/2017/11/09/starks_part_1.html) +- [STARKs, part 2: Thank Goodness it's FRI-day](https://vitalik.eth.limo/general/2017/11/22/starks_part_2.html) +- [STARKs, part 3: Into the Weeds](https://vitalik.eth.limo/general/2018/07/21/starks_part_3.html) + +Alan Szepieniec's STARK tutorials: + +- [Anatomy of a STARK](https://aszepieniec.github.io/stark-anatomy/) +- [BrainSTARK](https://aszepieniec.github.io/stark-brainfuck/) + +StarkWare's STARK Math blog series: + +- [STARK Math: The Journey Begins](https://medium.com/starkware/stark-math-the-journey-begins-51bd2b063c71) +- [Arithmetization I](https://medium.com/starkware/arithmetization-i-15c046390862) +- [Arithmetization II](https://medium.com/starkware/arithmetization-ii-403c3b3f4355) +- [Low Degree Testing](https://medium.com/starkware/low-degree-testing-f7614f5172db) +- [A Framework for Efficient STARKs](https://medium.com/starkware/a-framework-for-efficient-starks-19608ba06fbe) + +StarkWare's STARK tutorial: + +- [STARK 101](https://starkware.co/stark-101/) diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/design/_category_.yml b/versioned_docs/version-0.12 (stable)/miden-vm/design/_category_.yml new file mode 100644 index 0000000..485baed --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-vm/design/_category_.yml @@ -0,0 +1,4 @@ +label: "Design" +# Determines where this documentation section appears relative to other sections in the parent folder +position: 7 +collapsed: true diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/design/chiplets/_category_.yml b/versioned_docs/version-0.12 (stable)/miden-vm/design/chiplets/_category_.yml new file mode 100644 index 0000000..32a2c4e --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-vm/design/chiplets/_category_.yml @@ -0,0 +1,4 @@ +label: "Chiplets" +# Determines where this documentation section appears relative to other sections in the parent folder +position: 5 +collapsed: true diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/design/chiplets/ace.md b/versioned_docs/version-0.12 (stable)/miden-vm/design/chiplets/ace.md new file mode 100644 index 0000000..8d8b4ef --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-vm/design/chiplets/ace.md @@ -0,0 +1,510 @@ +--- +title: "ACE Chiplet" +sidebar_position: 5 +--- + +# ACE chiplet + +The following note describes the design and functionality of the Arithmetic Circuit Evaluation (ACE) chiplet. + +Given a description of an arithmetic circuit and a set of input values, it ensures this circuit evaluates to zero over these inputs. +Its main purpose is to reduce the number of cycles required when recursively verifying a STARK proof in Miden assembly. +In particular, it performs the DEEP-ALI constraint composition polynomial check over the evaluations of the trace column polynomials at the out-of-domain point. + +The chiplet expects the caller to have prepared a region of memory containing + +- **inputs** to circuit, +- **constants** which make up the circuit, +- **instructions** describing the arithmetic operations evaluated by the circuit. + +The term **variable** refers to a value, which is either an input or a constant. + +Mathematically, we can represent an arithmetic circuit as a DAG, where leaves correspond to inputs and constants, and the nodes are the operations computing the result (which must be zero). +For example, take the following composition polynomial to be evaluated by the chiplet + +$$ +s(s - 1) + \alpha \left[ s \cdot (\text{output} - 42) + (s - 1) \cdot (\text{output} - \text{input}) \right]. +$$ + +Given an input selector $s$, and input variables $\alpha, \text{input}, \text{output}$, the two combined constraints ensure that + +$$ +s \in \{0,1\}, \quad +\text{output} = +\begin{cases} +\text{input}, &s = 0, +42, &s = 1. +\end{cases} +$$ + +The following graph describes the evaluation of the circuit describing the above polynomial. The leaf nodes correspond to the variables of the polynomial: blue for inputs and green for constants. +Note that the circuit is able to reuse evaluation, as is the case for the node $s-1$. + +```mermaid +flowchart BT + %% Sink node (final expression) + add2["result = c₀ + α⋅c₁"] + mul1["s × (s−1)"] + mul4["α × c₁"] + add1["c₁ = s⋅case₌₁ + (s−1)⋅case₌₀"] + mul2["s × case₌₁"] + mul3["(s−1) × case₌₀"] + sub1["s − 1"] + sub2["case₌₁ = output − 42"] + sub4["case₌₀ = output − input"] + %% Leaf nodes + s["s"] + one1["1"] + alpha["α"] + input["input"] + const42["42"] + output_val["output"] + %% Classes: colored borders only + classDef blue stroke: #3498db, stroke-width: 2px; + classDef green stroke: #2ecc71, stroke-width: 2px; + class s,alpha,input,output_val blue; + class one1,const42 green; + %% Edges (pointing upward) + mul1 --> add2 + mul4 --> add2 + s --> mul1 + sub1 --> mul1 + s --> sub1 + one1 --> sub1 + alpha --> mul4 + add1 --> mul4 + mul2 --> add1 + mul3 --> add1 + s --> mul2 + sub2 --> mul2 + output_val --> sub2 + const42 --> sub2 + sub1 --> mul3 + sub4 --> mul3 + output_val --> sub4 + input --> sub4 +``` + +The chiplet constructs and verifies the correctness of such a DAG by using a logUp argument, which we interpret as a _wiring bus_. +In each row, the chiplet can either insert a new node with the next fresh identifier and desired value or request a node’s value by providing the identifier of a previously inserted node. +Whenever we create a new node in the DAG (when loading a variable or evaluating an operation), we “insert” it onto the wiring bus by emitting a tuple $(id,v)$ together with its final fan‐out count $m$. +In other words, at insertion time we record $(id,v)$ with multiplicity $m$, where $m$ is exactly the number of times this node will later be used as an input to downstream operations. +Then, each time some later instruction reads that node $(id,v)$, we “consume” one copy of $(id,v)$ - i.e., we remove it once from the wiring bus — decrementing the stored multiplicity by exactly 1. +By the time we finish inserting and consuming all nodes, two things must hold: + +1. The very last node we produced (the root of the DAG) has value 0. +2. Every inserted tuple has been consumed exactly $m$ times, so the wiring bus is empty. + +## Trace layout + +The ACE chiplet produces a trace with 16 internal columns. +Each _section_ of the trace corresponds to exactly one circuit evaluation. +Within each section, there are two ordered _blocks_: + +1. A READ block, which loads inputs/constants from memory and inserts them into the DAG. +2. An EVAL block, which executes each instruction — fetching two existing DAG nodes, computing $v_{out}$, and inserting a new node. + +In what follows, we'll refer to READ/EVAL blocks depending on which operation is being performed in a given row, though we sometimes refer to a row's _mode_ when it would conflict with the term _operation_. + +### Columns + +The following table describes the 16 columns used by the chiplet, and their interpretation in each block. + +| **BLOCK** | $s_{start}$ | $s_{block}$ | $ctx$ | $ptr$ | $clk$ | | $id_0$ | $v_{0,0}$ | $v_{0,1}$ | $id_1$ | $v_{1,0}$ | $v_{1,1}$ | | | | $m_0$ | +| --------- | ----------- | ----------- | ----- | ----- | ----- | ---- | ------ | --------- | --------- | ------ | --------- | --------- | ---------- | --------- | --------- | ----- | +| **READ** | = | = | = | = | = | | = | = | = | = | = | = | $n_{eval}$ | | $m_1$ | = | +| **EVAL** | = | = | = | = | = | $op$ | = | = | = | = | = | = | $id_2$ | $v_{2,0}$ | $v_{2,1}$ | = | + +- "=" means the interpretation is the same in both READ/EVAL. +- Empty cell means the cell is unused in this block. +- Unlabeled columns appear under a block‐specific heading instead. + +While we will later describe the purpose of each comment, we provide some intuition about how to interpret the labels themselves: + +- $s_{start}$ is a boolean selector indicating the first row of a section, +- $s_{block}$ is a boolean selector indicating the type of block the row is a part of (0 for READ, and 1 for EVAL), +- $(ctx, ptr, clk)$ identifies the memory request when reading variables or circuit instructions. +- $(id_i, v_{i,0}, v_{i,1})$ refers to a node in the evaluation graph at index $id_i$ whose value is the extension field element $v_i = (v_{i,0}, v_{i,1})$, for $i = 0, 1, 2$. +- $m_i$ is used when inserting a node $(id_i, v_i)$ into the evaluation graph, corresponding to its _fan-out degree_. It is also interpreted as the _multiplicity_ of the element being added to the wiring bus. + +### Memory layout + +The chiplet trace contains multiple sections, each performing a distinct circuit evaluation. +Within a section, the chiplet reads through a contiguous, word‐aligned memory region that contains: + +- **$I$ input variables** each stored as an extension‐field element, with two elements per word. +- **$C$ circuit constants** also stored two per word, which are part of the circuit description and hence included in its commitment. +- **$N$ circuit instructions** each encoded as one field‐element representing a packed triple $(op,id_l,id_r)$. + +The caller is responsible for writing the inputs and circuit into memory before invoking the chiplet. +If the same circuit is evaluated multiple times, the caller must overwrite the input region with the new inputs for each evaluation. +To start a circuit evaluation, the caller pushes one chiplet‐bus message: + +$$ +(\mathsf{ACE\_LABEL},ctx,ptr,clk,n_{read},n_{eval}), +$$ + +where: + +- $(ctx,clk)$ identifies the memory‐access context that every row will use. +- $ptr$ is the word-aligned pointer to the first input variable. +- $n_{read} = I + C$ is the total count of input and constant elements that the chiplet will read. +- $n_{eval} = N$ is the total number of arithmetic operations (instructions) the chiplet will evaluate. + +For both inputs and constants, it is permissible to pad their respective regions with zeros. +Any padded zero will simply be ignored by the instructions (they are not referenced by any instruction, so their multiplicity will be set to 0). +To ensure the entire region is word-aligned, we add up to three "dummy" instructions which square the last node. +This has no effect when the last evaluation is zero, as required. + +## Circuit evaluation + +The evaluation is initialized in the first row of the section by setting + +- $id_0 = n_{read} + n_{eval} -1$, indicating the expected number of nodes in the DAG. Whenever the chiplet adds a new wire to the bus, this identifier decrements by 1, until it reaches 0. +- $s_{start} = 1$, indicating the start of the section (it is set to 0 in all remaining rows) +- $s_{block} = 0$, ensuring the evaluation starts by reading variables +- $ctx, clk, n_{eval}, ptr$ provided by the bus request. $(ctx, clk)$ remain constant for the entire section, while $n_{eval}$ is constant only across the READ block. + +In every row, the chiplet the following actions in each block: + +**READ** (when $s_{block} = 0$): + +- Reads a word from memory at $(ctx,ptr,clk)$, containing two extension-field elements $(v_{0},v_{1})$. +- Assigns two new node IDs $(id_0,id_1)$ to those elements, where $id_1 = id_0 - 1$ +- Inserts each new node into the evaluation graph (wiring bus) with a specified fan-out count $m_i$. +- Increments $ptr$ by 4 in the next row. +- Decrements $id_0$ by 2 in the next row. +- If $id_0'$ in the next row is equal to $n_{eval}$, it switches to the EVAL operation by setting $s_{block}' = 1$ + +**EVAL** (when $s_{block} = 1$): + +- Reads a single field-element $instr$ (an encoded instruction) from memory at $(ctx,ptr,clk)$. +- Decodes $(op,id_1,id_2)$ from $instr$. +- Fetches the two input nodes $(id_1,v_1), (id_2, v_2)$ from the wiring bus, consuming one fan-out from each (i.e., with multiplicity $m_i = -1$). +- Computes + $$ + v_{0} = + \begin{cases} + v_1 - v_2, & op = -1, + v_1 \times v_2, & op = 0, + v_1 + v_2, & op = 1. + \end{cases} + $$ +- Inserts $(id_0,v_{0})$ onto the wiring bus with its final fan-out count $m_0$. +- Increments $ptr$ by 4 in the next row. +- If $id_0 = 0$, checks that $v_0 =0$ and ends the evaluation. +- Otherwise, decrements $id_0$ by 1 in the next row. + +_**Note**: Wire bus requests also include the memory access pair $(ctx, clk)$ which ensures that the wires produced by different circuit evaluations are distinct._ + +### Example + +The following is a section of the trace representing the evaluation of the expressions + +$$ +s(s - 1) + \alpha \left[ s \cdot (\text{output} - 42) + (s - 1) \cdot (\text{output} - \text{input}) \right]. +$$ + +We start by assigning values to both the inputs and constants nodes, stored in memory in addresses `0x0000 - 0x0005` + +$$ +\begin{aligned} +v_{14} &= \alpha, & v_{10} &= 42, +v_{13} &= \text{output}, & v_{9} &= 1. +v_{12} &= s, +v_{11} &= \text{input}, +\end{aligned} +$$ + +We can then write down the values of all evaluated nodes. +Their instructions are stored in the memory region `0x0006 - 0x00014` + +$$ +\begin{aligned} +v_{8} &= v_{12} - v_{9} &&|\ s - 1 +v_{7} &= v_{12} \times v_8 &&|\ c_0 = s \times (s - 1) +v_{6} &= v_{13} - v_{10} &&|\ \text{case}_{s=1} = \text{output} - 42 +v_{5} &= v_{13} - v_{11} &&|\ \text{case}_{s=0} = \text{output} - \text{input} +v_{4} &= v_{12} \times v_{6} &&|\ s \times \text{case}_{s=1} +v_{3} &= v_{8} \times v_{5} &&|\ (s-1) \times \text{case}_{s=0} +v_{2} &= v_{4} + v_{3} &&|\ c_1 = s × \text{case}_{s=1} + (s - 1) \times \text{case}_{s=0} +v_{1} &= v_{14} \times v_{2} &&|\ \alpha \times c_1 +v_{0} &= v_{7} + v_1 &&|\ \text{result} = c_0 + \alpha × c_1 +\end{aligned} +$$ + +| $s_{start}$ | $s_{block}$ | $ctx$ | $ptr$ | $clk$ | $op$ | $id_0$ | $v_{0}$ | $id_1$ | $v_{1}$ | $n_{eval}$/$id_{2}$ | $m_1$/$v_2$ | $m_0$ | +| ----------- | ----------- | ----- | ------ | ----- | -------- | ------ | ----------------------------- | ------ | ------------------------ | ------------------- | ----------------------- | ------------ | +| 1 | 0 | ctx | 0x0000 | clk | | 14 | $v_{14} = \alpha$ | 13 | $v_{13} = \text{input}$ | 8 | $m_{13} = 2$ | $m_{14} = 1$ | +| 0 | 0 | ctx | 0x0004 | clk | | 12 | $v_{12} = s$ | 11 | $v_{11} = \text{output}$ | 8 | $m_{11} = 1$ | $m_{12} = 3$ | +| 0 | 0 | ctx | 0x0008 | clk | | 10 | $v_{10} = 42$ | 9 | $v_{9} = 1$ | 8 | $m_{9} = 1$ | $m_{10} = 1$ | +| 0 | 1 | ctx | 0x000c | clk | $-$ | 8 | $v*{8} = v*{12} - v\_{9} $ | 12 | $v_{12} = s$ | 9 | $v_{9}$ | 2 | +| 0 | 1 | ctx | 0x000d | clk | $\times$ | 7 | $v_{7} = v_{12} \times v_8$ | 12 | $v_{12} = s$ | 8 | $v_{8}$ | 1 | +| 0 | 1 | ctx | 0x000e | clk | $-$ | 6 | $v_{6} = v_{13} - v_{10}$ | 13 | $v_{13} = \text{output}$ | 10 | $v_{10} = 42$ | 1 | +| 0 | 1 | ctx | 0x000f | clk | $-$ | 5 | $v_{5} = v_{13} - v_{11}$ | 13 | $v_{13} = \text{output}$ | 11 | $v_{11} = \text{input}$ | 1 | +| 0 | 1 | ctx | 0x0010 | clk | $\times$ | 4 | $v_{4} = v_{12} \times v_{6}$ | 12 | $v_{12} = s$ | 6 | $v_{6}$ | 1 | +| 0 | 1 | ctx | 0x0011 | clk | $\times$ | 3 | $v_{3} = v_{8} \times v_{5}$ | 8 | $v_{8}$ | 5 | $v_{5}$ | 1 | +| 0 | 1 | ctx | 0x0012 | clk | $+$ | 2 | $v_{2} = v_{4} + v_{3}$ | 4 | $v_{4}$ | 3 | $v_{3}$ | 1 | +| 0 | 1 | ctx | 0x0013 | clk | $\times$ | 1 | $v_{1} = v_{14} \times v_{2}$ | 14 | $v_{14} = \alpha$ | 2 | $v_{2}$ | 1 | +| 0 | 1 | ctx | 0x0014 | clk | $+$ | 0 | $v_{0} = v_{7} + v_{1}$ | 7 | $v_{7}$ | 1 | $v_{1}$ | 0 | + +## Constraints + +### Flags + +In this section, we derive boolean flags that indicate whether a row is at the boundary (first, last, or transition) of the ACE chiplet trace, a section, or a READ/EVAL block. + +#### Chiplet flags + +The chiplet trace activates different chiplet constraints using a common set of binary selectors $s_1, \ldots$. +While it is likely that the ACE chiplet will appear in third position, we derive the flags and boundary constraints for the general case where the chiplet appears in the d-th position. +Accounting for this degree allows us to evaluate whether we need a separate degree-1 internal selector for activating the chiplet's constraints. +The layout of the chiplet trace will look something like the following. + +| Chiplet | $s_1, \ldots, s_{d-1}$ | $s_{d}$ | ... | +| -------- | ---------------------- | ------------- | ------------- | +| Previous | $[1, ..., 1, 0]$ | $cols_{prev}$ | $cols_{prev}$ | +| ACE | $[1, ..., 1, 1]$ | $0$ | $cols_{ace}$ | +| Next | $[1, ..., 1, 1]$ | $1$ | $0$ | + +From these common selectors, we derive the following binary flags which indicate which portion of the ACE chiplet is active. + +- $f_{prev}$: The previous chiplet is active. +- $f_{ace}$: The ACE chiplet is active. +- $f_{ace, first}'$: Next row is the first row in ACE chiplet. +- $f_{ace, next}$: Current and next rows are in ACE chiplet. +- $f_{ace, last}$: Last row in ACE chiplet. + +> $$ +> \begin{aligned} +> f_{prev} &\gets (1 - s_{d-1}) \cdot \prod_{i=1}^{d-2} s_{i} && | \deg = d-1 +> f_{ace} &\gets (1 - s_{d}) \cdot \prod_{i=1}^{d-1} s_{i} && | \deg = d +> f_{ace, first}' &\gets f_{prev} \cdot (1 - s_{d-1}') && | \deg = d +> f_{ace, next} &\gets f_{ace} \cdot (1 - s_{d}') && | \deg = d + 1 +> f_{ace, last} &\gets f_{ace} \cdot s_{d}' && | \deg = d + 1 +> \end{aligned} +> $$ + +### Section and block flags + +The selector $s_{start}$ indicates the start of a new section, from which we can derive the following flags indicating which part of the section the current row is in: + +- $f_{start}$: the current row initializes the section. +- $f_{next}$: the current and next rows are in the same section. +- $f_{end}$: the current row finalizes the section. + +> $$ +> \begin{aligned} +> f_{start} &\gets f_{ace} \cdot s_{start} && | \deg = d+1 +> f_{next} &\gets f_{ace, next} \cdot (1 - s_{start}') && | \deg = d+2 +> f_{end} &\gets f_{ace, next} \cdot s_{start}' + f_{ace,last} && | \deg = d+2 +> \end{aligned} +> $$ + +These flags require the following constraints on $s_{start}$. + +- it is binary. +- it must equal 1 in the first row. +- it must equal 0 in the last row. +- two consecutive rows cannot initialize a section, so a section contains at least two rows. + +> $$ +> \begin{aligned} +> f_{ace} \cdot s_{start} \cdot (1 - s_{start}) &= 0 && | \deg = d + 2 +> f_{ace, first}' \cdot (1 - s_{start}') &= 0 && | \deg = d + 1 +> f_{ace, last} \cdot s_{start} &= 0 && | \deg = d + 2 +> f_{ace, next} \cdot s_{start} \cdot s_{start}' &= 0 && | \deg = d + 2. +> \end{aligned} +> $$ + +A section is composed of a READ block followed by an EVAL block. +The flag indicating which block is active is derived from the binary selector $s_{block}$. +These constraints ensure they are mutually exclusive + +> $$ +> \begin{aligned} +> f_{read} \gets (1-s_{block}) & &&| \deg = 1 +> f_{eval} \gets s_{block} & &&| \deg = 1 +> +> f_{ace} \cdot (1-s_{block}) \cdot s_{block} = 0 && &| \deg = d + 2 +> \end{aligned} +> $$ + +The following constraints ensure the proper layout of the trace. In particular, it contains one or more sections each with consecutive READ and EVAL blocks. + +- The first row cannot be EVAL, so it must be READ. +- A row after EVAL cannot be READ. +- The last row cannot be READ, so it must be EVAL. + +> $$ +> \begin{aligned} +> f_{start} \cdot f_{eval} &= 0 && | \deg = d + 2 +> f_{next} \cdot f_{eval} \cdot f_{read}' &= 0 && | \deg = d + 4 +> f_{end} \cdot f_{read} &= 0 && | \deg = d + 3 +> \end{aligned} +> $$ + +In particular, we can infer from the above that + +- each section contains at least two rows (a READ and an EVAL row), and, +- a row following a READ is always in the same section. + +A READ row checks whether $id_0'$ in the next row is equal to $n_{eval} - 1$ provided by the caller at initialization, +in which case we ensure the following row is an EVAL. +Otherwise, $n_{eval}$ remains the same. + +> $$ +> f_{ace} \cdot f_{read} \cdot +> \big[f_{read}' \cdot n_{eval}' + f_{eval}' \cdot (id_0' + 1) - n_{eval}\big] = 0 \quad | \deg = d + 3. +> $$ + +### Section constraints + +These constraints apply to all rows within the same section/ + +- Across the section, $ctx$ and $clk$ are constant. +- A READ/EVAL block requests a word/element from memory, so the $ptr$ increases by 4/1, respectively in the next row. +- A READ/EVAL block adds 2/1 new nodes to the evaluation graph, so $id_0$ decreases by that amount in the next row. + +> $$ +> \begin{aligned} +> f_{next} \cdot (ctx' - ctx) &= 0 && | \deg = d + 3 +> f_{next} \cdot (clk' - clk) &= 0 && | \deg = d + 3 +> f_{next} \cdot \big[ptr' - ptr + 4 \cdot f_{read} + f_{eval}\big] &= 0 && | \deg = d + 3 +> f_{next} \cdot \big[id_0 - id_0' + 2 \cdot f_{read} + f_{eval}\big] &= 0 && | \deg = d + 3 +> \end{aligned} +> $$ + +### READ constraints + +In a READ block, each row requests a row from memory a word containing two extension field elements $v_0 = (v_{0,0}, v_{0,1})$ and $v_1 = (v_{1,0}, v_{1,1})$. +The [wire bus section](#wire-bus) describes how both of these nodes are inserted into the wire bus. + +The only constraint we enforce is that $id_0$ and $id_1$ are consecutive + +> $$ +> \begin{aligned} +> f_{ace} \cdot f_{read} \cdot (id_1 - id_0 + 1) &= 0 && | \deg = d + 2 +> \end{aligned} +> $$ + +### EVAL constraints + +An EVAL block checks that the arithmetic operation $op$ was correctly applied to inputs $v_1, v_2$ and results in $v_0$. +The result is given by the degree-4 expression + +> $$ +> v_{out} \gets op^2 \cdot \big[ v_1 + op\cdot v_2 \big] + (1 - op^2) \cdot \big[ v_1 \cdot v_2 \big] +> = \begin{cases} +> v_1 - v_2, & op = -1, +> v_1 \times v_2, & op = 0, +> v_1 + v_2, & op = 1. +> \end{cases} +> $$ + +The output node is correctly evaluated when: + +- $op \in \{-1, 0, 1\}$ is a valid arithmetic operation, and, +- $v_0$ is equal to $v_{out}$. + +> $$ +> \begin{aligned} +> f_{ace} \cdot f_{eval} \cdot op \cdot (op^2 - 1) &= 0 && | \deg = d + 4 +> f_{ace} \cdot f_{eval} \cdot (v_0 - v_{out}) &= 0 && | \deg = d + 5 +> \end{aligned} +> $$ + +The actual instruction is given by the field element $instr$ read from memory. It encodes + +- the operation $op$ using 2 bits +- the ids of $v_1$ and $v_2$ using 30 bits each and are packed as + > $$ + > instr \gets id_0 + id_1 \cdot 2^{30} + (op+1)\cdot 2^{60}. + > $$ + +It is clear from the constraint on $op$ that $op+1$ will always require 2 bits, and that range constraints on $id_1, id_2$ are unnecessary. +These ids are sent as-is to the wire bus with multiplicity $-1$. +For the logUp argument to be valid, the section must include a row where a node is added to the circuit with the same id such that the pole $\frac{-1}{w_i}$ can be annihilated. +The only way to do so is if there exists a corresponding $id_0$ matching the one in the instruction. +This is ensured by the pointers given by the chiplet bus message initializing the section, and the constraint enforcing it to be strictly increasing in each row. +Therefore, as long as the trusted circuit contains fewer than $2^{30}$ ids, the $id_1$ and $id_2$ values can never overflow this bound. + +To ensure the circuit has finished evaluating and that the final output value is 0, we enforce that the node with $id_0 = 0$ has value $v_0 = 0$ in the last row of the section. + +> $$ +> \begin{aligned} +> f_{end} \cdot id_0 &= 0 && | \deg = d + 3 +> f_{end} \cdot v_0 &= 0 && | \deg = d + 3 +> \end{aligned} +> $$ + +### Wire bus + +Each row of the chip makes up to 3 requests to the circuit's wire bus. +For $i = 0, 1, 2$, each request has the form $(ctx, clk, id_i, v_{i,0}, v_{i,1})$, which uniquely identifies a node in the DAG representing the evaluation of the circuit. +Sending this message to the bus can be viewed as updating the total degree of the node in the graph. +When performing a READ operation, a node is added to the graph, and we set its degree update $e_i$ to be equal to its final fan-out degree at the end of the evaluation. +This value is also referred to as the _multiplicity_ $m_i$. +When a node is used as an input of an arithmetic operation, we set $e_i = - 1$. + +The expression $e_i$ is derived from $m_i$ and the operation flag, so that the wire bus update is uniform across all rows of the chiplet's trace. + +- $v_0$ always defines a new node, and each operation defines its identifier $id_0$ and multiplicity $m_0$ using the same columns. + > $$ + > e_0 \gets m_0 \quad \text{| degree} = 1 + > $$ +- $v_1$ defines a new node when the operation is a READ, but is an input during an EVAL. Again, the columns for these values are identical. + > $$ + > e_1 \gets f_{read} \cdot m_1 - f_{eval} \quad \text{| degree} = 2 + > $$ +- $v_2$ is unused during a READ, and an input during EVAL + > $$ + > e_2 \gets - f_{eval} \quad \text{| degree} = 1 + > $$ + +The auxiliary logUp bus column $b_{wire}$ is updated as follows. +Given random challenges $\alpha_j$ for $j = 0, ..., 5$, let $w_i = \alpha_0 + \alpha_1 \cdot ctx + \alpha_2 \cdot clk + \alpha_3 \cdot id_i + \alpha_4 \cdot v_{i,0} + \alpha_5 \cdot v_{i,1}$ be the randomized node value. +The value of the bus in the next column is given by + +> $$ +> b_{wire}' = b_{wire} + \sum_{i=0}^2 \frac{e_i}{w_i}, +> $$ + +The actual constraint is given by normalizing the denominator + +> $$ +> f_{ace}\cdot \left( (b_{wire} - b_{wire}') \cdot \prod_{i=0}^{2}w_i + \left(e_0 \cdot w_1 \cdot w_2 + e_1 \cdot w_0 \cdot w_2 + e_2 \cdot w_0 \cdot w_1\right)\right) = 0 \quad \text{| degree} = d + 4. +> $$ + +### Chiplet and Virtual table bus + +The ACE chiplet initializes a circuit evaluation by responding to a request made by the decoder, through the [chiplet bus](./index.md#chiplets-bus) $b_{chip}$. +As mentioned earlier, the message corresponds to the tuple, which is sent to the bus only when $f_{start} = 1$. +$$(\mathsf{ACE_LABEL}, ctx, ptr, clk, n_\text{read}, n_\text{eval}).$$ +The value $n_{read}$ is computed as $id_0 - 1 - n_{eval}$, since in the first row, $id_0$ is expected to be equal to the total number of nodes inserted (subtracting 1 since identifiers are indexed from zero). +We refer to the [chiplet bus constraints](./index.md#chiplets-bus-constraints) which describes the constraints for chiplet bus responses. + +The requests sent to the memory chiplet cannot use the same chiplet bus, as the decoder requests saturate the degree of constraint over its auxiliary column. +Instead, we use the [virtual table bus](./index.md#chiplets-virtual-table) $v_{table}$ which extends the chiplet bus. + +In each row, the chiplet makes one of the two following requests to the memory chiplet depending on which block it is in: + +- $(\mathsf{MEMORY\_READ\_WORD\_LABEL}, ctx, ptr, clk, v_{0,0}, v_{0,1}, v_{1,0}, v_{1,1})$, when $f_{read} = 1$. +- $(\mathsf{MEMORY\_READ\_ELEMENT\_LABEL}, ctx, ptr, clk, instr)$, when $f_{eval} = 1$. + +The values are obtained as-is from the current row, except for the instruction which is given by + +$$ +instr \gets id_0 + id_1 \cdot 2^{30} + (op+1)\cdot 2^{60}. +$$ + +As mentioned earlier, it encodes a circuit instruction applying the arithmetic operation $op \in \{- ,\times, +\}$ (mapped to the range $[0,1,2]$) to the nodes with identifiers $id_1, id_2 \in [0, 2^{30}[$. + +As usual, the messages are randomly reduced using by challenges $\alpha_0, \alpha_1, \ldots$, resulting in the degree-1 expressions +$u_{mem, read}$ and $u_{mem, eval}$, respectively. + +Since the virtual table bus is used exclusively by the chiplets trace, it must be constrained in this chiplet: + +> $$ +> f_{ace} \cdot \Big( v_{table}' \cdot \big( f_{read}\cdot w_{mem,read} + f_{eval}\cdot w_{mem,eval} \big) - v_{table}\Big) = 0 \quad | \deg = d+3. +> $$ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/design/chiplets/bitwise.md b/versioned_docs/version-0.12 (stable)/miden-vm/design/chiplets/bitwise.md new file mode 100644 index 0000000..cb9ba50 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-vm/design/chiplets/bitwise.md @@ -0,0 +1,166 @@ +--- +title: "Bitwise Chiplet" +sidebar_position: 3 +--- + +# Bitwise chiplet + +In this note we describe how to compute bitwise AND and XOR operations on 32-bit values and the constraints required for proving correct execution. + +Assume that $a$ and $b$ are field elements in a 64-bit prime field. Assume also that $a$ and $b$ are known to contain values smaller than $2^{32}$. We want to compute $a \oplus b \rightarrow z$, where $\oplus$ is either bitwise AND or XOR, and $z$ is a field element containing the result of the corresponding bitwise operation. + +First, observe that we can compute AND and XOR relations for **single bit values** as follows: + +$$ +and(a, b) = a \cdot b +$$ + +$$ +xor(a, b) = a + b - 2 \cdot a \cdot b +$$ + +To compute bitwise operations for multi-bit values, we will decompose the values into individual bits, apply the operations to single bits, and then aggregate the bitwise results into the final result. + +To perform this operation we will use a table with 12 columns, and computing a single AND or XOR operation will require 8 table rows. We will also rely on two periodic columns as shown below. + +![bitwise_execution_trace](../../img/design/chiplets/bitwise/bitwise_execution_trace.png) + +In the above, the columns have the following meanings: + +- Periodic columns $k_0$ and $k_1$. These columns contain values needed to switch various constraints on or off. $k_0$ contains a single one, followed by a repeating sequence of seven zeros. $k_1$ contains a repeating sequence of seven ones, followed by a single zero. +- Input columns $a$ and $b$. On the first row of each 8-row cycle, the prover will set values in these columns to the upper 4 bits of the values to which a bitwise operation is to be applied. For all subsequent rows, we will append the next-most-significant 4-bit limb to each value. Thus, by the final row columns $a$ and $b$ will contain the full input values for the bitwise operation. +- Columns $a_0$, $a_1$, $a_2$, $a_3$, $b_0$, $b_1$, $b_2$, $b_3$ will contain lower 4 bits of their corresponding values. +- Output column $z_p$. This column represents the value of column $z$ for the prior row. For the first row, it is set to $0$. +- Output column $z$. This column will be used to aggregate the results of bitwise operations performed over columns $a_0$, $a_1$, $a_2$, $a_3$, $b_0$, $b_1$, $b_2$, $b_3$. By the time we get to the last row in each 8-row cycle, this column will contain the final result. + +## Example + +Let's illustrate the above table on a concrete example. For simplicity, we'll use 16-bit values, and thus, we'll only need 4 rows to complete the operation (rather than 8 for 32-bit values). Let's say $a = 41851$ (`b1010_0011_0111_1011`) and $b = 40426$ (`b1001_1101_1110_1010`), then $and(a, b) = 33130$ (`b1000_0001_0110_1010`). The table for this computation looks like so: + +| a | b | a0 | a1 | a2 | a3 | b0 | b1 | b2 | b3 | zp | z | +| :---: | :---: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :----: | :---: | +| 10 | 9 | 0 | 1 | 0 | 1 | 1 | 0 | 0 | 1 | 0 | 8 | +| 163 | 157 | 1 | 1 | 0 | 0 | 1 | 0 | 1 | 1 | 8 | 129 | +| 2615 | 2526 | 1 | 1 | 1 | 0 | 0 | 1 | 1 | 1 | 129 | 2070 | +| 41851 | 40426 | 1 | 1 | 0 | 1 | 0 | 1 | 0 | 1 | 2070 | 33130 | + +Here, in the first row, we set each of the $a$ and $b$ columns to the value of their most-significant 4-bit limb. The bit columns ($a_0 .. a_3$ and $b_0 .. b_3$) in the first row contain the lower 4 bits of their corresponding values (`b1010` and `b1001`). Column $z$ contains the result of bitwise AND for the upper 4 bits (`b1000`), while column $z_p$ contains that result for the prior row. + +With every subsequent row, we inject the next-most-significant 4 bits of each value into the bit columns, increase the $a$ and $b$ columns accordingly, and aggregate the result of bitwise AND into the $z$ column, adding it to $2^4$ times the value of $z$ in the previous row. We set column $z_p$ to be the value of $z$ in the prior row. By the time we get to the last row, the $z$ column contains the result of the bitwise AND, while columns $a$ and $b$ contain their original values. + +## Constraints + +AIR constraints needed to ensure the correctness of the above table are described below. We also add one more column $s$ to the execution trace, to allow us to select between two bitwise operations (`U32AND` and `U32XOR`). + +### Selectors + +The Bitwise chiplet supports two operations with the following operation selectors: + +- `U32AND`: $s = 0$ +- `U32XOR`: $s = 1$ + +The constraints must require that the selectors be binary and stay the same throughout the cycle: + +> $$ +> s^2 - s = 0 \text{ | degree} = 2 +> $$ + +> $$ +> k_1 \cdot (s' - s) = 0 \text{ | degree} = 2 +> $$ + +### Input decomposition + +We need to make sure that inputs $a$ and $b$ are decomposed correctly into their individual bits. To do this, first, we need to make sure that columns $a_0$, $a_1$, $a_2$, $a_3$, $b_0$, $b_1$, $b_2$, $b_3$, can contain only binary values ($0$ or $1$). This can be accomplished with the following constraints (for $i$ ranging between $0$ and $3$): + +> $$ +> a_i^2 - a_i = 0 \text{ | degree} = 2 +> $$ + +> $$ +> b_i^2 - b_i = 0 \text{ | degree} = 2 +> $$ + +Then, we need to make sure that on the first row of every 8-row cycle, the values in the columns $a$ and $b$ are exactly equal to the aggregation of binary values contained in the individual bit columns $a_i$, and $b_i$. This can be enforced with the following constraints: + +> $$ +> k_0 \cdot \left(a - \sum_{i=0}^3(2^i \cdot a_i)\right) = 0 \text{ | degree} = 2 +> $$ + +> $$ +> k_0 \cdot \left(b - \sum_{i=0}^3(2^i \cdot b_i)\right) = 0 \text{ | degree} = 2 +> $$ + +The above constraints enforce that when $k_0 = 1$, $a = \sum_{i=0}^3(2^i \cdot a_i)$ and $b = \sum_{i=0}^3(2^i \cdot b_i)$. + +Lastly, we need to make sure that for all rows in an 8-row cycle except for the last one, the values in $a$ and $b$ columns are increased by the values contained in the individual bit columns $a_i$ and $b_i$. Denoting $a$ as the value of column $a$ in the current row, and $a'$ as the value of column $a$ in the next row, we can enforce these conditions as follows: + +> $$ +> k_1 \cdot \left(a' - \left(a \cdot 16 + \sum_{i=0}^3(2^i \cdot a'_i)\right)\right) = 0 \text{ | degree} = 2 +> $$ + +> $$ +> k_1 \cdot \left(b' - \left(b \cdot 16 + \sum_{i=0}^3(2^i \cdot b'_i)\right)\right) = 0 \text{ | degree} = 2 +> $$ + +The above constraints enforce that when $k_1 = 1$ , $a' = 16 \cdot a + \sum_{i=0}^3(2^i \cdot a'_i)$ and $b' = 16 \cdot b + \sum_{i=0}^3(2^i \cdot b'_i)$. + +### Output aggregation + +To ensure correct aggregation of operations over individual bits, first we need to ensure that in the first row, the aggregated output value of the previous row should be 0. +> $$ +> k_0 \cdot z_p = 0 \text{ | degree} = 2 +> $$ + +Next, we need to ensure that for each row except the last, the aggregated output value must equal the previous aggregated output value in the next row. +> $$ +> k_1 \cdot \left(z - z'_p\right) = 0 \text{ | degree} = 2 +> $$ + +Lastly, we need to ensure that for all rows the value in the $z$ column is computed by multiplying the previous output value (from the $z_p$ column in the current row) by 16 and then adding it to the bitwise operation applied to the row's set of bits of $a$ and $b$. The entire constraint must also be multiplied by the operation selector flag to ensure it is only applied for the appropriate operation. + +For `U32AND`, this is enforced with the following constraint: + +> $$ +> (1 - s) \cdot \left(z -(z_p \cdot 16 + \sum_{i=0}^3(2^i \cdot a_i \cdot b_i))\right) = 0 \text{ | degree} = 3 +> $$ + +For `U32XOR`, this is enforced with the following constraint: + +> $$ +> s \cdot \left(z -(z_p \cdot 16 + \sum_{i=0}^3(2^i \cdot (a_i + b_i - 2 \cdot a_i \cdot b_i)))\right) = 0 \text{ | degree} = 3 +> $$ + +## Chiplets bus constraints + +To simplify the notation for describing bitwise constraints on the chiplets bus, we'll first define variable $u$, which represents how $a$, $b$, and $z$ in the execution trace are reduced to a single value. Denoting the random values received from the verifier as $\alpha_0, \alpha_1$, etc., this can be achieved as follows. + +$$ +u = \alpha_0 + \alpha_1 \cdot op_{bit} + \alpha_2 \cdot a + \alpha_3 \cdot b + \alpha_4 \cdot z +$$ + +Where, $op_{bit}$ is the unique [operation label](./index.md#operation-labels) of the bitwise operation. + +The request side of the constraint for the bitwise operation is described in the [stack bitwise operation section](../stack/u32_ops.md#u32and). + +To provide the results of bitwise operations to the chiplets bus, we want to include values of $a$, $b$ and $z$ at the last row of the cycle. + +First, we'll define another intermediate variable $v_i$. It will include $u$ into the product when $k_1 = 0$. ($u_i$ represents the value of $u$ for row $i$ of the trace.) + +$$ +v_i = (1-k_1) \cdot u_i +$$ + +Then, setting $m = 1 - k_1$, we can compute the permutation product from the bitwise chiplet as follows: + +$$ +\prod_{i=0}^n (v_i \cdot m_i + 1 - m_i) +$$ + +The above ensures that when $1 - k_1 = 0$ (which is true for all rows in the 8-row cycle except for the last one), the product does not change. Otherwise, $v_i$ gets included into the product. + +The response side of the bus communication can be enforced with the following constraint: + +> $$ +> b'_{chip} = b_{chip} \cdot (v_i \cdot m_i + 1 - m_i) \text{ | degree} = 4 +> $$ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/design/chiplets/hasher.md b/versioned_docs/version-0.12 (stable)/miden-vm/design/chiplets/hasher.md new file mode 100644 index 0000000..5530de5 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-vm/design/chiplets/hasher.md @@ -0,0 +1,452 @@ +--- +title: "Hash Chiplet" +sidebar_position: 2 +--- + +# Hash chiplet + +Miden VM "offloads" all hash-related computations to a separate _hash processor_. This chiplet supports executing the [Rescue Prime Optimized](https://eprint.iacr.org/2022/1577) hash function (or rather a [specific instantiation](https://docs.rs/miden-crypto/latest/miden_crypto/hash/rpo/struct.Rpo256.html) of it) in the following settings: + +- A single permutation of Rescue Prime Optimized. +- A simple 2-to-1 hash. +- A linear hash of $n$ field elements. +- Merkle path verification. +- Merkle root update. + +The chiplet can be thought of as having a small instruction set of $11$ instructions. These instructions are listed below, and examples of how these instructions are used by the chiplet are described in the following sections. + +| Instruction | Description | +| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `HR` | Executes a single round of the VM's native hash function. All cycles which are not one less than a multiple of $8$ execute this instruction. That is, the chiplet executes this instruction on cycles $0, 1, 2, 3, 4, 5, 6$, but not $7$, and then again, $8, 9, 10, 11, 12, 13, 14$, but not $15$ etc. | +| `BP` | Initiates computation of a single permutation, a 2-to-1 hash, or a linear hash of many elements. This instruction can be executed only on cycles which are multiples of $8$, and it can also be executed concurrently with an `HR` instruction. | +| `MP` | Initiates Merkle path verification computation. This instruction can be executed only on cycles which are multiples of $8$, and it can also be executed concurrently with an `HR` instruction. | +| `MV` | Initiates Merkle path verification for the "old" node value during Merkle root update computation. This instruction can be executed only on cycles which are multiples of $8$, and it can also be executed concurrently with an `HR` instruction. | +| `MU` | Initiates Merkle path verification for the "new" node value during Merkle root update computation. This instruction can be executed only on cycles which are multiples of $8$, and it can also be executed concurrently with an `HR` instruction. | +| `HOUT` | Returns the result of the currently running computation. This instruction can be executed only on cycles which are one less than a multiple of $8$ (e.g. $7$, $15$ etc.). | +| `SOUT` | Returns the whole hasher state. This instruction can be executed only on cycles which are one less than a multiple of $8$, and only if the computation was started using `BP` instruction. | +| `ABP` | Absorbs a new set of elements into the hasher state when computing a linear hash of many elements. This instruction can be executed only on cycles which are one less than a multiple of $8$, and only if the computation was started using `BP` instruction. | +| `MPA` | Absorbs the next Merkle path node into the hasher state during Merkle path verification computation. This instruction can be executed only on cycles which are one less than a multiple of $8$, and only if the computation was started using `MP` instruction. | +| `MVA` | Absorbs the next Merkle path node into the hasher state during Merkle path verification for the "old" node value during Merkle root update computation. This instruction can be executed only on cycles which are one less than a multiple of $8$, and only if the computation was started using `MV` instruction. | +| `MUA` | Absorbs the next Merkle path node into the hasher state during Merkle path verification for the "new" node value during Merkle root update computation. This instruction can be executed only on cycles which are one less than a multiple of $8$, and only if the computation was started using `Mu` instruction. | + +## Chiplet trace + +Execution trace table of the chiplet consists of $16$ trace columns and $3$ periodic columns. The structure of the table is such that a single permutation of the hash function can be computed using $8$ table rows. The layout of the table is illustrated below. + +![hash_execution_trace](../../img/design/chiplets/hasher/hash_execution_trace.png) + +The meaning of the columns is as follows: + +- Three periodic columns $k_0$, $k_1$, and $k_2$ are used to help select the instruction executed at a given row. All of these columns contain patterns which repeat every $8$ rows. For $k_0$ the pattern is $7$ zeros followed by $1$ one, helping us identify the last row in the cycle. For $k_1$ the pattern is $6$ zeros, $1$ one, and $1$ zero, which can be used to identify the second-to-last row in a cycle. For $k_2$ the pattern is $1$ one followed by $7$ zeros, which can identify the first row in the cycle. +- Three selector columns $s_0$, $s_1$, and $s_2$. These columns can contain only binary values (ones or zeros), and they are also used to help select the instruction to execute at a given row. +- Twelve hasher state columns $h_0, ..., h_{11}$. These columns are used to hold the hasher state for each round of the hash function permutation. The state is laid out as follows: + - The first four columns ($h_0, ..., h_3$) are reserved for capacity elements of the state. When the state is initialized for hash computations, $h_0$ should be set to $0$ if the number of elements to be hashed is a multiple of the rate width ($8$). Otherwise, $h_0$ should be set to $1$. $h_1$ should be set to the domain value if a domain has been provided (as in the case of [control block hashing](../programs.md#program-hash-computation)). All other capacity elements should be set to $0$'s. + - The next eight columns ($h_4, ..., h_{11}$) are reserved for the rate elements of the state. These are used to absorb the values to be hashed. Once the permutation is complete, hash output is located in the first four rate columns ($h_4, ..., h_7$). +- One index column $i$. This column is used to help with Merkle path verification and Merkle root update computations. + +In addition to the columns described above, the chiplet relies on two running product columns which are used to facilitate multiset checks (similar to the ones described [here](https://hackmd.io/@relgabizon/ByFgSDA7D)). These columns are: + +- $b_{chip}$ - which is used to tie the chiplet table with the main VM's stack and decoder. That is, values representing inputs consumed by the chiplet and outputs produced by the chiplet are multiplied into $b_{chip}$, while the main VM stack (or decoder) divides them out of $b_{chip}$. Thus, if the sets of inputs and outputs between the main VM stack and hash chiplet are the same, the value of $b_{chip}$ should be equal to $1$ at the start and the end of the execution trace. +- $p_1$ - which is used to keep track of the _sibling_ table used for Merkle root update computations. Specifically, when a root for the old leaf value is computed, we add an entry for all sibling nodes to the table (i.e., we multiply $p_1$ by the values representing these entries). When the root for the new leaf value is computed, we remove the entries for the nodes from the table (i.e., we divide $p_1$ by the value representing these entries). Thus, if both computations used the same set of sibling nodes (in the same order), the sibling table should be empty by the time Merkle root update procedure completes (i.e., the value of $p_1$ would be $1$). + +## Instruction flags + +As mentioned above, chiplet instructions are encoded using a combination of periodic and selector columns. These columns can be used to compute a binary flag for each instruction. Thus, when a flag for a given instruction is set to $1$, the chiplet executes this instruction. Formulas for computing instruction flags are listed below. + +| Flag | Value | Notes | +| ---------- | ----------------------------------------------------- | ------------------------------------------------------------------------------------------------- | +| $f_{rpr}$ | $1 - k_0$ | Set to $1$ on the first $7$ steps of every $8$-step cycle. | +| $f_{bp}$ | $k_2 \cdot s_0 \cdot (1 - s_1) \cdot (1 - s_2)$ | Set to $1$ when selector flags are $(1, 0, 0)$ on rows which are multiples of $8$. | +| $f_{mp}$ | $k_2 \cdot s_0 \cdot (1 - s_1) \cdot s_2$ | Set to $1$ when selector flags are $(1, 0, 1)$ on rows which are multiples of $8$. | +| $f_{mv}$ | $k_2 \cdot s_0 \cdot s_1 \cdot (1 - s_2)$ | Set to $1$ when selector flags are $(1, 1, 0)$ on rows which are multiples of $8$. | +| $f_{mu}$ | $k_2 \cdot s_0 \cdot s_1 \cdot s_2$ | Set to $1$ when selector flags are $(1, 1, 1)$ on rows which are multiples of $8$. | +| $f_{hout}$ | $k_0 \cdot (1 - s_0) \cdot (1 - s_1) \cdot (1 - s_2)$ | Set to $1$ when selector flags are $(0, 0, 0)$ on rows which are $1$ less than a multiple of $8$. | +| $f_{sout}$ | $k_0 \cdot (1 - s_0) \cdot (1 - s_1) \cdot s_2$ | Set to $1$ when selector flags are $(0, 0, 1)$ on rows which are $1$ less than a multiple of $8$. | +| $f_{abp}$ | $k_0 \cdot s_0 \cdot (1 - s_1) \cdot (1 - s_2)$ | Set to $1$ when selector flags are $(1, 0, 0)$ on rows which are $1$ less than a multiple of $8$. | +| $f_{mpa}$ | $k_0 \cdot s_0 \cdot (1 - s_1) \cdot s_2$ | Set to $1$ when selector flags are $(1, 0, 1)$ on rows which are $1$ less than a multiple of $8$. | +| $f_{mva}$ | $k_0 \cdot s_0 \cdot s_1 \cdot (1 - s_2)$ | Set to $1$ when selector flags are $(1, 1, 0)$ on rows which are $1$ less than a multiple of $8$. | +| $f_{mua}$ | $k_0 \cdot s_0 \cdot s_1 \cdot s_2$ | Set to $1$ when selector flags are $(1, 1, 1)$ on rows which are $1$ less than a multiple of $8$. | + +A few additional notes about flag values: + +- With the exception of $f_{rpr}$, all flags are mutually exclusive. That is, if one flag is set to $1$, all other flats are set to $0$. +- With the exception of $f_{rpr}$, computing flag values involves $3$ multiplications, and thus the degree of these flags is $4$. +- We can also define a flag $f_{out} = k_0 \cdot (1 - s_0) \cdot (1 - s_1)$. This flag will be set to $1$ when either $f_{hout}=1$ or $f_{sout}=1$ in the current row. +- We can define a flag $f_{out}' = k_1 \cdot (1 - s_0') \cdot (1 - s_1')$. This flag will be set to $1$ when either $f_{hout}=1$ or $f_{sout}=1$ in the next row. + +We also impose the following restrictions on how values in selector columns can be updated: + +- Values in columns $s_1$ and $s_2$ must be copied over from one row to the next, unless $f_{out} = 1$ or $f_{out}' = 1$ indicating the `hout` or `sout` flag is set for the current or the next row. +- Value in $s_0$ must be set to $1$ if $f_{out}=1$ for the previous row, and to $0$ if any of the flags $f_{abp}$, $f_{mpa}$, $f_{mva}$, or $f_{mua}$ are set to $1$ for the previous row. + +The above rules ensure that we must finish one computation before starting another, and we can't change the type of the computation before the computation is finished. + +## Computation examples + +### Single permutation + +Computing a single permutation of Rescue Prime Optimized hash function involves the following steps: + +1. Initialize hasher state with $12$ field elements. +2. Apply Rescue Prime Optimized permutation. +3. Return the entire hasher state as output. + +The chiplet accomplishes the above by executing the following instructions: + +``` +[BP, HR] // init state and execute a hash round (concurrently) +HR HR HR HR HR HR // execute 6 more hash rounds +SOUT // return the entire state as output +``` + +Execution trace for this computation would look as illustrated below. + +![hash_1_permutation_trace](../../img/design/chiplets/hasher/hash_1_permutation_trace.png) + +In the above $\{a_0, ..., a_{11}\}$ is the input state of the hasher, and $\{b_0, ..., b_{11}\}$ is the output state of the hasher. + +### Simple 2-to-1 hash + +Computing a 2-to-1 hash involves the following steps: + +1. Initialize hasher state with $8$ field elements, setting the second capacity element to $domain$ if the domain is provided (as in the case of [control block hashing](../programs.md#program-hash-computation)) or else $0$, and the remaining capacity elements to $0$. +2. Apply Rescue Prime Optimized permutation. +3. Return elements $[4, 8)$ of the hasher state as output. + +The chiplet accomplishes the above by executing the following instructions: + +``` +[BP, HR] // init state and execute a hash round (concurrently) +HR HR HR HR HR HR // execute 6 more hash rounds +HOUT // return elements 4, 5, 6, 7 of the state as output +``` + +Execution trace for this computation would look as illustrated below. + +![hash_2_to_1_hash](../../img/design/chiplets/hasher/hash_2_to_1_hash.png) + +In the above, we compute the following: + +$$ +\{c_0, c_1, c_2, c_3\} \leftarrow hash(\{a_0, a_1, a_2, a_3\}, \{b_0, b_1, b_2, b_3\}) +$$ + +### Linear hash of n elements + +Computing a linear hash of $n$ elements consists of the following steps: + +1. Initialize hasher state with the first $8$ elements, setting the first capacity register to $0$ if $n$ is a multiple of the rate width ($8$) or else $1$, and the remaining capacity elements to $0$. +2. Apply Rescue Prime Optimized permutation. +3. Absorb the next set of elements into the state (up to $8$ elements), while keeping capacity elements unchanged. +4. Repeat steps 2 and 3 until all $n$ elements have been absorbed. +5. Return elements $[4, 8)$ of the hasher state as output. + +The chiplet accomplishes the above by executing the following instructions (for hashing $16$ elements): + +``` +[BP, HR] // init state and execute a hash round (concurrently) +HR HR HR HR HR HR // execute 6 more hash rounds +ABP // absorb the next set of elements into the state +HR HR HR HR HR HR HR // execute 7 hash rounds +HOUT // return elements 4, 5, 6, 7 of the state as output +``` + +Execution trace for this computation would look as illustrated below. + +![hash_linear_hash_n](../../img/design/chiplets/hasher/hash_linear_hash_n.png) + +In the above, the value absorbed into hasher state between rows $7$ and $8$ is the delta between values $t_i$ and $s_i$. Thus, if we define $b_i = t_i - s_i$ for $i \in [0, 8)$, the above computes the following: + +$$ +\{r_0, r_1, r_2, r_3\} \leftarrow hash(a_0, ..., a_7, b_0, ..., b_7) +$$ + +### Verify Merkle path + +Verifying a Merkle path involves the following steps: + +1. Initialize hasher state with the leaf and the first node of the path, setting all capacity elements to $0$s. + a. Also, initialize the index register to the leaf's index value. +2. Apply Rescue Prime Optimized permutation. + a. Make sure the index value doesn't change during this step. +3. Copy the result of the hash to the next row, and absorb the next node of the Merkle path into the hasher state. + a. Remove a single bit from the index, and use it to determine how to place the copied result and absorbed node in the state. +4. Repeat steps 2 and 3 until all nodes of the Merkle path have been absorbed. +5. Return elements $[4, 8)$ of the hasher state as output. + a. Also, make sure the index value has been reduced to $0$. + +The chiplet accomplishes the above by executing the following instructions (for Merkle tree of depth $3$): + +``` +[MP, HR] // init state and execute a hash round (concurrently) +HR HR HR HR HR HR // execute 6 more hash rounds +MPA // copy result & absorb the next node into the state +HR HR HR HR HR HR HR // execute 7 hash rounds +HOUT // return elements 4, 5, 6, 7 of the state as output +``` + +Suppose we have a Merkle tree as illustrated below. This Merkle tree has $4$ leaves, each of which consists of $4$ field elements. For example, leaf $a$ consists of elements $a_0, a_1, a_2, a_3$, leaf be consists of elements $b_0, b_1, b_2, b_3$ etc. + +![hash_merkle_tree](../../img/design/chiplets/hasher/hash_merkle_tree.png) + +If we wanted to verify that leaf $d$ is in fact in the tree, we'd need to compute the following hashes: + +$$ +r \leftarrow hash(e, hash(c, d)) +$$ + +And if $r = g$, we can be convinced that $d$ is in fact in the tree at position $3$. Execution trace for this computation would look as illustrated below. + +![hash_merkle_tree_trace](../../img/design/chiplets/hasher/hash_merkle_tree_trace.png) + +In the above, the prover provides values for nodes $c$ and $e$ non-deterministically. + +### Update Merkle root + +Updating a node in a Merkle tree (which also updates the root of the tree) can be simulated by verifying two Merkle paths: the path that starts with the old leaf and the path that starts with the new leaf. + +Suppose we have the same Merkle tree as in the previous example, and we want to replace node $d$ with node $d'$. The computations we'd need to perform are: + +$$ +r \leftarrow hash(e, hash(c, d)) +r' \leftarrow hash(e, hash(c, d')) +$$ + +Then, as long as $r = g$, and the same values were used for $c$ and $e$ in both computations, we can be convinced that the new root of the tree is $r'$. + +The chiplet accomplishes the above by executing the following instructions: + +``` +// verify the old merkle path +[MV, HR] // init state and execute a hash round (concurrently) +HR HR HR HR HR HR // execute 6 more hash rounds +MPV // copy result & absorb the next node into the state +HR HR HR HR HR HR HR // execute 7 hash rounds +HOUT // return elements 4, 5, 6, 7 of the state as output + +// verify the new merkle path +[MU, HR] // init state and execute a hash round (concurrently) +HR HR HR HR HR HR // execute 6 more hash rounds +MPU // copy result & absorb the next node into the state +HR HR HR HR HR HR HR // execute 7 hash rounds +HOUT // return elements 4, 5, 6, 7 of the state as output +``` + +The semantics of `MV` and `MU` instructions are similar to the semantics of `MP` instruction from the previous example (and `MVA` and `MUA` are similar to `MPA`) with one important difference: `MV*` instructions add the absorbed node (together with its index in the tree) to permutation column $p_1$, while `MU*` instructions remove the absorbed node (together with its index in the tree) from $p_1$. Thus, if the same nodes were used during both Merkle path verification, the state of $p_1$ should not change. This mechanism is used to ensure that the same internal nodes were used in both computations. + +## AIR constraints + +When describing AIR constraints, we adopt the following notation: for column $x$, we denote the value in the current row simply as $x$, and the value in the next row of the column as $x'$. Thus, all transition constraints described in this note work with two consecutive rows of the execution trace. + +### Selector columns constraints + +For selector columns, first we must ensure that only binary values are allowed in these columns. This can be done with the following constraints: + +$$ +s_0^2 - s_0 = 0 \text{ | degree} = 2 +s_1^2 - s_1 = 0 \text{ | degree} = 2 +s_2^2 - s_2 = 0 \text{ | degree} = 2 +$$ + +Next, we need to make sure that unless $f_{out}=1$ or $f_{out}'=1$, the values in columns $s_1$ and $s_2$ are copied over to the next row. This can be done with the following constraints: + +$$ +(s_1' - s_1) \cdot (1 - f_{out}') \cdot (1 - f_{out}) = 0 \text{ | degree} = 7 +(s_2' - s_2) \cdot (1 - f_{out}') \cdot (1 - f_{out}) = 0 \text{ | degree} = 7 +$$ + +Next, we need to enforce that if any of $f_{abp}, f_{mpa}, f_{mva}, f_{mua}$ flags is set to $1$, the next value of $s_0$ is $0$. In all other cases, $s_0$ should be unconstrained. These flags will only be set for rows that are 1 less than a multiple of 8 (the last row of each cycle). This can be done with the following constraint: + +$$ +s_0' \cdot (f_{abp} + f_{mpa} + f_{mva} + f_{mua})= 0 \text{ | degree} = 5 +$$ + +Lastly, we need to make sure that no invalid combinations of flags are allowed. This can be done with the following constraints: + +$$ +k_0 \cdot (1 - s_0) \cdot s_1 = 0 \text{ | degree} = 3 +$$ + +The above constraints enforce that on every step which is one less than a multiple of $8$, if $s_0 = 0$, then $s_1$ must also be set to $0$. Basically, if we set $s_0=0$, then we must make sure that either $f_{hout}=1$ or $f_{sout}=1$. + +### Node index constraints + +Node index column $i$ is relevant only for Merkle path verification and Merkle root update computations, but to simplify the overall constraint system, the same constraints will be imposed on this column for all computations. + +Overall, we want values in the index column to behave as follows: + +- When we start a new computation, we should be able to set $i$ to an arbitrary value. +- When a computation is finished, value in $i$ must be $0$. +- When we absorb a new node into the hasher state we must shift the value in $i$ by one bit to the right. +- In all other cases value in $i$ should not change. + +A shift by one bit to the right can be described with the following equation: $i = 2 \cdot i' + b$, where $b$ is the value of the bit which is discarded. Thus, as long as $b$ is a binary value, the shift to the right is performed correctly, and this can be enforced with the following constraint: + +$$ +b^2 - b = 0 +$$ + +Since we want to enforce this constraint only when a new node is absorbed into the hasher state, we'll define a flag for when this should happen as follows: + +$$ +f_{an} = f_{mp} + f_{mv} + f_{mu} + f_{mpa} + f_{mva} + f_{mua} +$$ + +And then the full constraint would looks as follows: + +$$ +f_{an} \cdot (b^2 - b) = 0 \text{ | degree} = 6 +$$ + +Next, to make sure when a computation is finished $i=0$, we can use the following constraint: + +$$ +f_{out} \cdot i = 0 \text{ | degree} = 4 +$$ + +Finally, to make sure that the value in $i$ is copied over to the next row unless we are absorbing a new row or the computation is finished, we impose the following constraint: + +$$ +(1 - f_{an} - f_{out}) \cdot (i' - i) = 0 \text{ | degree} = 5 +$$ + +To satisfy these constraints for computations not related to Merkle paths (i.e., 2-to-1 hash and liner hash of elements), we can set $i = 0$ at the start of the computation. This guarantees that $i$ will remain $0$ until the end of the computation. + +### Hasher state constraints + +Hasher state columns $h_0, ..., h_{11}$ should behave as follows: + +- For the first $7$ row of every $8$-row cycle (i.e., when $k_0=0$), we need to apply [Rescue Prime Optimized](https://eprint.iacr.org/2022/1577) round constraints to the hasher state. For brevity, we omit these constraints from this note. +- On the $8$th row of every $8$-row cycle, we apply the constraints based on which transition flag is set as described in the table below. + +Specifically, when absorbing the next set of elements into the state during linear hash computation (i.e., $f_{abp} = 1$), the first $4$ elements (the capacity portion) are carried over to the next row. For $j \in [0, 4)$ this can be described as follows: + +$$ +f_{abp} \cdot (h'_j - h_j) = 0 \text{ | degree} = 5 +$$ + +When absorbing the next node during Merkle path computation (i.e., $f_{mp} + f_{mv} + f_{mu}=1$), the result of the previous hash ($h_4, ..., h_7$) are copied over either to $(h_4', ..., h_7')$ or to $(h_8', ..., h_{11}')$ depending on the value of $b$, which is defined in the same way as in the previous section. For $j \in [0, 4)$ this can be described as follows: + +$$ +(f_{mp} + f_{mv} + f_{mu}) \cdot ((1 - b) \cdot (h_{j +4}' - h_{j+4}) + b \cdot (h_{j + 8}' - h_{j + 4})) = 0 \text{ | degree} = 6 +$$ + +Note, that when a computation is completed (i.e., $f_{out}=1$), the next hasher state is unconstrained. + +### Multiset check constraints + +In this sections we describe constraints which enforce updates for [multiset check columns](../lookups/multiset.md) $b_{chip}$ and $p_1$. These columns can be updated only on rows which are multiples of $8$ or $1$ less than a multiple of $8$. On all other rows the values in the columns remain the same. + +To simplify description of the constraints, we define the following variables. Below, we denote random values sent by the verifier after the prover commits to the main execution trace as $\alpha_0$, $\alpha_1$, $\alpha_2$ etc. + +$$ +m = op_{label} + 2^4 \cdot k_0 + 2^5 \cdot k_2 +v_h = \alpha_0 + \alpha_1 \cdot m + \alpha_2 \cdot (clk + 1) + \alpha_3 \cdot i +v_a = \sum_{j=0}^{3}(\alpha_{j+4} \cdot h_j) +v_b = \sum_{j=4}^{7}(\alpha_{j+4} \cdot h_j) +v_c = \sum_{j=8}^{11}(\alpha_{j+4} \cdot h_j) +v_d = \sum_{j=8}^{11}(\alpha_j \cdot h_j) +$$ + +In the above: + +- $m$ is a _transition label_, composed of the [operation label](./index.md#operation-labels) and the periodic columns that uniquely identify each transition function. The values in the $k_0$ and $k_2$ periodic columns are included to identify the row in the hash cycle where the operation occurs. They serve to differentiate between operations that share selectors but occur at different rows in the cycle, such as `BP`, which uses $op_{linhash}$ at the first row in the cycle to initiate a linear hash, and `ABP`, which uses $op_{linhash}$ at the last row in the cycle to absorb new elements. +- $v_h$ is a _common header_ which is a combination of the transition label, a unique row address, and the node index. For the unique row address, the `clk` column from the system component is used, but we add $1$, because the system's `clk` column starts at $0$. +- $v_a$, $v_b$, $v_c$ are the first, second, and third words (4 elements) of the hasher state. +- $v_d$ is the third word of the hasher state but computed using the same $\alpha$ values as used for the second word. This is needed for computing the value of $v_{leaf}$ below to ensure that the same $\alpha$ values are used for the leaf node regardless of which part of the state the node comes from. + +#### Chiplets bus constraints + +As described previously, the [chiplets bus](./index.md#chiplets-bus) $b_{chip}$, implemented as a running product column, is used to tie the hash chiplet with the main VM's stack and decoder. When receiving inputs from or returning results to the stack (or decoder), the hash chiplet multiplies $b_{chip}$ by their respective values. On the other side, when sending inputs to the hash chiplet or receiving results from the chiplet, the stack (or decoder) divides $b_{chip}$ by their values. + +In the section below we describe only the hash chiplet side of the constraints (i.e., multiplying $b_{chip}$ by relevant values). We define the values which are to be multiplied into $b_{chip}$ for each operation as follows: + +When starting a new simple or linear hash computation (i.e., $f_{bp}=1$) or when returning the entire state of the hasher ($f_{sout}=1$), the entire hasher state is included into $b_{chip}$: + +$$ +v_{all} = v_h + v_a + v_b + v_c +$$ + +When starting a Merkle path computation (i.e., $f_{mp} + f_{mv} + f_{mu} = 1$), we include the leaf of the path into $b_{chip}$. The leaf is selected from the state based on value of $b$ (defined as in the previous section): + +$$ +v_{leaf} = v_h + (1-b) \cdot v_b + b \cdot v_d +$$ + +When absorbing a new set of elements into the state while computing a linear hash (i.e., $f_{abp}=1$), we include deltas between the last $8$ elements of the hasher state (the rate) into $b_{chip}$: + +$$ +v_{abp} = v_h + v'_b + v'_c - (v_b + v_c) +$$ + +When a computation is complete (i.e., $f_{hout}=1$), we include the second word of the hasher state (the result) into $b_{chip}$: + +$$ +v_{res} = v_h + v_b +$$ + +Using the above values, we can describe the constraints for updating column $b_{chip}$ as follows. + +$$ +b_{chip}' = b_{chip} \cdot ((f_{bp} + f_{sout}) \cdot v_{all} + (f_{mp} + f_{mv} + f_{mu}) \cdot v_{leaf} + f_{abp} \cdot v_{abp} + f_{hout} \cdot v_{res} + +1 - (f_{bp} + f_{mp} + f_{mv} + f_{mu} + f_{abp} + f_{out})) +$$ + +The above constraint reduces to the following under various flag conditions: + +| Condition | Applied constraint | +| -------------- | ------------------------------------- | +| $f_{bp} = 1$ | $b_{chip}' = b_{chip} \cdot v_{all}$ | +| $f_{sout} = 1$ | $b_{chip}' = b_{chip} \cdot v_{all}$ | +| $f_{mp} = 1$ | $b_{chip}' = b_{chip} \cdot v_{leaf}$ | +| $f_{mv} = 1$ | $b_{chip}' = b_{chip} \cdot v_{leaf}$ | +| $f_{mu} = 1$ | $b_{chip}' = b_{chip} \cdot v_{leaf}$ | +| $f_{abp} = 1$ | $b_{chip}' = b_{chip} \cdot v_{abp}$ | +| $f_{hout} = 1$ | $b_{chip}' = b_{chip} \cdot v_{res}$ | +| Otherwise | $b_{chip}' = b_{chip}$ | + +Note that the degree of the above constraint is $7$. + +#### Sibling table constraints + +_Note: Although this table is described independently, it is implemented as part of the [chiplets virtual table](../chiplets/index.md#chiplets-virtual-table), which combines all virtual tables required by any of the chiplets into a single master table._ + +As mentioned previously, the sibling table (represented by running column $p_1$) is used to keep track of sibling nodes used during Merkle root update computations. For this computation, we need to enforce the following rules: + +- When computing the old Merkle root, whenever a new sibling node is absorbed into the hasher state (i.e., $f_{mv} + f_{mva} = 1$), an entry for this sibling should be included into $p_1$. +- When computing the new Merkle root, whenever a new sibling node is absorbed into the hasher state (i.e., $f_{mu} + f_{mua} = 1$), the entry for this sibling should be removed from $p_1$. + +To simplify the description of the constraints, we use variables $v_b$ and $v_c$ defined above and define the value representing an entry in the sibling table as follows: + +$$ +v_{sibling} = \alpha_0 + \alpha_3 \cdot i + b \cdot v_b + (1-b) \cdot v_c +$$ + +Using the above value, we can define the constraint for updating $p_1$ as follows: + +$$ +p_1' \cdot \left( (f_{mv} + f_{mva}) \cdot v_{sibling} + 1 - (f_{mv} + f_{mva}) \right) = +p_1 \cdot \left( (f_{mu} + f_{mua}) \cdot v_{sibling} + 1 - (f_{mu} + f_{mua}) \right) +$$ + +The above constraint reduces to the following under various flag conditions: + +| Condition | Applied constraint | +| ------------- | ------------------------------ | +| $f_{mv} = 1$ | $p_1' \cdot v_{sibling} = p_1$ | +| $f_{mva} = 1$ | $p_1' \cdot v_{sibling} = p_1$ | +| $f_{mu} = 1$ | $p_1' = p_1 \cdot v_{sibling}$ | +| $f_{mua} = 1$ | $p_1' = p_1 \cdot v_{sibling}$ | +| Otherwise | $p_1' = p_1$ | + +Note that the degree of the above constraint is $7$. + +To make sure computation of the old Merkle root is immediately followed by the computation of the new Merkle root, we impose the following constraint: + +$$ +(f_{bp} + f_{mp} + f_{mv}) \cdot (1 - p_1) = 0 \text{ | degree} = 5 +$$ + +The above means that whenever we start a new computation which is not the computation of the new Merkle root, the sibling table must be empty. Thus, after the hash chiplet computes the old Merkle root, the only way to clear the table is to compute the new Merkle root. + +Together with boundary constraints enforcing that $p_1=1$ at the first and last rows of the running product column which implements the sibling table, the above constraints ensure that if a node was included into $p_1$ as a part of computing the old Merkle root, the same node must be removed from $p_1$ as a part of computing the new Merkle root. These two boundary constraints are described as part of the [chiplets virtual table constraints](../chiplets/index.md#chiplets-virtual-table-constraints). diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/design/chiplets/index.md b/versioned_docs/version-0.12 (stable)/miden-vm/design/chiplets/index.md new file mode 100644 index 0000000..e0ab826 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-vm/design/chiplets/index.md @@ -0,0 +1,254 @@ +--- +title: "Chiplets" +sidebar_position: 1 +--- + +# Chiplets + +The Chiplets module contains specialized components dedicated to accelerating complex computations. Each chiplet specializes in executing a specific type of computation and is responsible for proving both the correctness of its computations and its own internal consistency. + +Currently, Miden VM relies on 4 chiplets: + +- The [Hash Chiplet](./hasher.md) (also referred to as the Hasher), used to compute Rescue Prime Optimized hashes both for sequential hashing and for Merkle tree hashing. +- The [Bitwise Chiplet](./bitwise.md), used to compute bitwise operations (e.g., `AND`, `XOR`) over 32-bit integers. +- The [Memory Chiplet](./memory.md), used to support random-access memory in the VM. +- The [Arithmetic Circuit Evaluation (ACE)](./ace.md), used to ensure that arithmetic circuits evaluate to zero. +- The [Kernel ROM Chiplet](kernel_rom.md), used to enable executing kernel procedures during the [`SYSCALL` operation](../programs.md#syscall-block). + +Each chiplet executes its computations separately from the rest of the VM and proves the internal correctness of its execution trace in a unique way that is specific to the operation(s) it supports. These methods are described by each chiplet’s documentation. + +## Chiplets module trace + +The execution trace of the Chiplets module is generated by stacking the execution traces of each of its chiplet components. Because each chiplet is expected to generate significantly fewer trace rows than the other VM components (i.e., the decoder, stack, and range checker), stacking them enables the same functionality without adding as many columns to the execution trace. + +Each chiplet is identified within the Chiplets module by one or more chiplet selector columns which cause its constraints to be selectively applied. + +The result is an execution trace of 18 trace columns, which allows space for the widest chiplet component (the hash chiplet) and a column to select for it. + +_**Note**: The following diagram is outdated (see [issue #1829](https://github.com/0xMiden/miden-vm/issues/1829))._ + +![chiplets](../../img/design/chiplets/chiplets.png) + +During the finalization of the overall execution trace, the chiplets' traces (including internal selectors) are appended to the trace of the Chiplets module one after another, as pictured. Thus, when one chiplet's trace ends, the trace of the next chiplet starts in the subsequent row. + +Additionally, a padding segment is added to the end of the Chiplets module's trace so that the number of rows in the table always matches the overall trace length of the other VM processors, regardless of the length of the chiplet traces. The padding will simply contain zeroes. + +### Chiplets order + +The order in which the chiplets are stacked is determined by the requirements of each chiplet, including the width of its execution trace and the degree of its constraints. + +For simplicity, all of the "cyclic" chiplets which operate in multi-row cycles and require starting at particular row increments should come before any non-cyclic chiplets, and these should be ordered from longest-cycle to shortest-cycle. This avoids any additional alignment padding between chiplets. + +After that, chiplets are ordered by degree of constraints so that higher-degree chiplets get lower-degree chiplet selector flags. + +The resulting order is as follows: + +| Chiplet | Cycle Length | Internal Degree | Chiplet Selector Degree | Total Degree | Columns | Chiplet Selector Flag | +| --------------- | :----------: | :-------------: | :---------------------: | :----------: | :-----: | --------------------- | +| Hash chiplet | 8 | 8 | 1 | 9 | 17 | $\{0\}$ | +| Bitwise chiplet | 8 | 3 | 2 | 5 | 13 | $\{1, 0\}$ | +| Memory | - | 6 | 3 | 9 | 12 | $\{1, 1, 0\}$ | +| ACE | - | 5 | 4 | 9 | 16 | $\{1, 1, 1, 0\}$ | +| Kernel ROM | - | 3 | 5 | 8 | 5 | $\{1, 1, 1, 1, 0\}$ | +| Padding | - | - | - | - | - | $\{1, 1, 1, 1, 1\}$ | + +### Additional requirements for stacking execution traces + +Stacking the chiplets introduces one new complexity. Each chiplet proves its own correctness with its own set of internal transition constraints, many of which are enforced between each row in its trace and the next row. As a result, when the chiplets are stacked, transition constraints applied to the final row of one chiplet will cause a conflict with the first row of the following chiplet. + +This is true for any transition constraints that are applied at every row and selected by a `Chiplet Selector Flag` for the current row. (Therefore, cyclic transition constraints controlled by periodic columns do not cause any issue.) + +This requires the following adjustments for each chiplet. + +**In the hash chiplet:** there is no conflict, and therefore no change, since all constraints are periodic. + +**In the bitwise chiplet:** there is no conflict, and therefore no change, since all constraints are periodic. + +**In the memory chiplet:** all transition constraints cause a conflict. To adjust for this, the selector flag for the memory chiplet is designed to exclude its last row. Thus, memory constraints will not be applied when transitioning from the last row of the memory chiplet to the following row. This is achieved without any additional increase in the degree of constraints by using $s'_2$ as a selector instead of $s_2$ as seen [below](#chiplet-constraints). + +**In the ACE chiplet:** some transition constraints must be disabled in the last row. The flags are derived both from the chiplet selectors and are described in the [flags and boundary constraints section](./ace.md#flags). + +**In the kernel ROM chiplet:** the transition constraints referring to the $s_{first}'$ column cause a conflict. +It is resolved by enforcing the initial value of this selector in the last row of the previous chiplet, +and disabling the hash equality constraint in the last row. + +## Operation labels + +Each operation supported by the chiplets is given a unique identifier to ensure that the requests and responses sent to the [chiplets bus](#chiplets-bus) ($b_{chip}$) are indeed processed by the intended chiplet for that operation and that chiplets which support more than one operation execute the correct one. + +The labels are composed from the flag values of the chiplet selector(s) and internal operation selectors (if applicable). +The unique label of the operation is computed as the binary aggregation of the combined selectors plus $1$, note that the combined flag is represented in big-endian, so the bit representation below is reversed. + +| Operation | Chiplet & Internal
Selector Flags | Label | Value | +| ---------------------- | ---------------------------------------- | --------------- | ----- | +| `HASHER_LINEAR_HASH` | $\{0 \,\|\, 1, 0, 0\}$ | `1 + 0b001_0` | 3 | +| `HASHER_MP_VERIFY` | $\{0 \,\|\, 1, 0, 1\}$ | `1 + 0b101_0` | 11 | +| `HASHER_MR_UPDATE_OLD` | $\{0 \,\|\, 1, 1, 0\}$ | `1 + 0b011_0` | 7 | +| `HASHER_MR_UPDATE_NEW` | $\{0 \,\|\, 1, 1, 1\}$ | `1 + 0b111_0` | 15 | +| `HASHER_RETURN_HASH` | $\{0 \,\|\, 0, 0, 0\}$ | `1 + 0b000_0` | 1 | +| `HASHER_RETURN_STATE` | $\{0 \,\|\, 0, 0, 1\}$ | `1 + 0b100_0` | 9 | +| `BITWISE_AND` | $\{1, 0 \,\|\, 0\}$ | `1 + 0b0_01` | 2 | +| `BITWISE_XOR` | $\{1, 0 \,\|\, 1\}$ | `1 + 0b1_01` | 6 | +| `MEMORY_WRITE_ELEMENT` | $\{1, 1, 0 \,\|\, 0, 0\}$ | `1 + 0b00_011` | 4 | +| `MEMORY_WRITE_WORD` | $\{1, 1, 0 \,\|\, 0, 1\}$ | `1 + 0b10_011` | 20 | +| `MEMORY_READ_ELEMENT` | $\{1, 1, 0 \,\|\, 1, 0\}$ | `1 + 0b01_011` | 12 | +| `MEMORY_READ_WORD` | $\{1, 1, 0 \,\|\, 1, 1\}$ | `1 + 0b11_011` | 28 | +| `ACE_INIT` | $\{1, 1, 1, 0 \,\|\, - \}$ | `1 + 0b_0111` | 8 | +| `KERNEL_PROC_CALL` | $\{1, 1, 1, 1, 0 \,\|\, 0\}$ | `1 + 0b0_01111` | 16 | +| `KERNEL_PROC_INIT` | $\{1, 1, 1, 1, 0 \,\|\, 1\}$ | `1 + 0b1_01111` | 48 | + +## Chiplets module constraints + +### Chiplet constraints + +Each chiplet's internal constraints are defined in the documentation for the individual chiplets. To ensure that constraints are only ever selected for one chiplet at a time, the module's selector columns $s_0, s_1, s_2, s_3, s_4$ are combined into flags. Each chiplet's internal constraints are multiplied by its chiplet selector flag, and the degree of each constraint is correspondingly increased. + +This gives the following sets of constraints: + +> $$ +> (1 - s_0) \cdot c_{hash} = 0 \text{ | degree} = 1 + \deg(c_{hash}) +> $$ + +> $$ +> s_0 \cdot (1 - s_1) \cdot c_{bitwise} = 0 \text{ | degree} = 2 + \deg(c_{bitwise}) +> $$ + +> $$ +> s_0 \cdot s_1 \cdot (1 - s'_2) \cdot c_{memory} = 0 \text{ | degree} = 3 + \deg(c_{memory}) +> $$ + +> $$ +> s_0 \cdot s_1 \cdot (s_2) \cdot (1 - s'_3) \cdot c_{ace} = 0 \text{ | degree} = 4 + \deg(c_{ace}) +> $$ + +> $$ +> s_0 \cdot s_1 \cdot (s_2) \cdot (s_3) \cdot (1 - s'_4) \cdot c_{krom} = 0 \text{ | degree} = 5 + \deg(c_{krom}) +> $$ + +In the above: + +- $c_{hash}, c_{bitwise}, c_{memory}, c_{ace}, c_{krom}$ each represent an internal constraint from the indicated chiplet. +- $\deg(c)$ indicates the degree of the specified constraint. +- flags are applied in a like manner for all internal constraints in each respective chiplet. +- the selector for the memory chiplet excludes the last row of the chiplet (as discussed [above](#additional-requirements-for-stacking-execution-traces)). + +### Chiplet selector constraints + +We also need to ensure that the chiplet selector columns are set correctly. Although there are three columns for chiplet selectors, the stacked trace design means that they do not all act as selectors for the entire trace. Thus, selector constraints should only be applied to selector columns when they are acting as selectors. + +- $s_0$ acts as a selector for the entire trace. +- $s_1$ acts as a selector column when $s_0 = 1$. +- $s_2$ acts as a selector column when $s_0 = 1$ and $s_1 = 1$. +- $s_3$ acts as a selector column when $s_0 = 1$, $s_1 = 1$, and $s_2 = 1$. +- $s_4$ acts as a selector column when $s_0 = 1$, $s_1 = 1$, $s_2 = 1$, and $s_3 = 1$. + +Two conditions must be enforced for columns acting as chiplet selectors. + +1. When acting as a selector, the value in the selector column must be binary. +2. When acting as a selector, the value in the selector column may only change from $0 \rightarrow 1$. + +The following constraints ensure that selector values are binary. + +> $$ +> s_0^2 - s_0 = 0 \text{ | degree} = 2 +> s_0 \cdot (s_1^2 - s_1) = 0 \text{ | degree} = 3 +> s_0 \cdot s_1 \cdot (s_2^2 - s_2) = 0 \text{ | degree} = 4 +> s_0 \cdot s_1 \cdot s_2 \cdot (s_3^2 - s_3) = 0 \text{ | degree} = 5 +> s_0 \cdot s_1 \cdot s_2 \cdot s_3 \cdot (s_4^2 - s_4) = 0 \text{ | degree} = 6 +> $$ + +The following constraints ensure that the chiplets are stacked correctly by restricting selector values so they can only change from $0 \rightarrow 1$. + +> $$ +> s_0 \cdot (s_0 - s'_0) = 0 \text{ | degree} = 2 +> s_0 \cdot s_1 \cdot (s_1 - s'_1) \text{ | degree} = 3 +> s_0 \cdot s_1 \cdot s_2 \cdot (s_2 - s'_2) \text{ | degree} = 4 +> s_0 \cdot s_1 \cdot s_2 \cdot s_3 \cdot (s_3 - s'_3) \text{ | degree} = 5 +> s_0 \cdot s_1 \cdot s_2 \cdot s_3 \cdot s_4 \cdot (s_4 - s'_4) \text{ | degree} = 6 +> $$ + +In other words, the above constraints enforce that if a selector is $0$ in the current row, then it must be either $0$ or $1$ in the next row; if it is $1$ in the current row, it must be $1$ in the next row. + +## Chiplets bus + +The chiplets must be explicitly connected to the rest of the VM in order for it to use their operations. This connection must prove that all specialized operations which a given VM component claimed to offload to one of the chiplets were in fact executed by the correct chiplet with the same set of inputs and outputs as those used by the offloading component. + +This is achieved via a [bus](../lookups/index.md#communication-buses-in-miden-vm) called $b_{chip}$ where a request can be sent to any chiplet and a corresponding response will be sent back by that chiplet. + +The bus is implemented as a single [running product column](../lookups/multiset.md) where: + +- Each request is “sent” by computing an operation-specific lookup value from an [operation-specific label](#operation-labels), the operation inputs, and the operation outputs, and then dividing it out of the $b_{chip}$ running product column. +- Each chiplet response is “sent” by computing the same operation-specific lookup value from the label, inputs, and outputs, and then multiplying it into the $b_{chip}$ running product column. + +Thus, if the requests and responses match, then the bus column $b_{chip}$ will start and end with the value $1$. This condition is enforced by boundary constraints on the $b_{chip}$ column. +It is also possible to invoke chiplet computations through public inputs, by initializing the bus with requests that must be responded to by the chiplets. +In this case, the verifier computes the product of all randomness-reduced requests, treating it as a constant when evaluating the boundary constraint for the initial value of the bus column. +Currently, this is only used to request the initialization of kernel procedure digests for the kernel ROM chiplet. + +Note that the order of the requests and responses does not matter, as long as they are all included in $b_{chip}$. In fact, requests and responses for the same operation will generally occur at different cycles. + +### Chiplets bus constraints + +The chiplets bus constraints are defined by the components that use it to communicate. + +Lookup requests are sent to the chiplets bus by the following components: + +- The stack sends requests for [bitwise](../stack/u32_ops.md#u32and), [memory](../stack/io_ops.md#memory-access-operations), and [cryptographic hash operations](../stack/crypto_ops.md). +- The decoder sends requests for [hash operations](../decoder/index.md#program-block-hashing) for program block hashing. +- The decoder sends a procedure access request to the [Kernel ROM chiplet](./kernel_rom.md) for each `SYSCALL` during [program block hashing](../decoder/index.md#program-block-hashing). +- The verifier initializes the bus with requests to the [Kernel ROM chiplet](./kernel_rom.md) for each unique kernel procedure digest. + +Responses are provided by the [hash](./hasher.md#chiplets-bus-constraints), [bitwise](./bitwise.md#chiplets-bus-constraints), [memory](./memory.md#chiplets-bus-constraints), and [kernel ROM](./kernel_rom.md#chiplets-bus-constraints) chiplets. + +The chiplet bus can be initialized with randomness-reduced requests $v_0, v_1, \ldots$ by computing their product $v_{init} = \prod_i v_i$, and enforcing the boundary constraint in the first row to ensure +$b_{chip} = \frac{1}{v_{init}}$. +Note that $v_{init}$ is a constant, and therefore does not contribute to the constraint degree. + +> $$ +> b_{chip} \cdot v_{init} - 1 = 0 \text{ | degree} = 1 +> $$ + +## Chiplets virtual table + +_Note: over time, the use of this construction has evolved to the point where its name doesn't match the way it is used. This is documented in [issue #1779](https://github.com/0xMiden/miden-vm/issues/1779)._ + +The [virtual table](../lookups/multiset.md#virtual-tables) bus $vt_{chip}$ is used by several chiplets as a way to maintain and enforce the correctness of their internal states, and enable communication with each other. + +The hasher chiplet uses it as a way to store [sibling nodes](./hasher.md#sibling-table-constraints) when performing a Merkle tree update. +In particular, it expects an empty bus at the start of this operation, and ensures that all entries it inserts are removed once the new tree is finalized. +Consequently, the column representing this table must be equal to 1 at the boundaries of the hasher chiplet's trace, preventing communication with other chiplets. + +Other chiplets use the table as an extension of the chiplet bus, since both multi-sets are merged in the last row of the overall trace. + +This enables chiplets to make bus requests to other chiplets, without affecting the degree of the chiplet bus. +As currently implemented, a single constraint is required to include all requests made by the main trace and corresponding responses from the chiplets. +The degree of this constraint is the maximum of both message types and is currently reached by the requests by the main trace, +preventing chiplets from performing any requests using the same bus. +Instead, a chiplet can make a request through the $vt_{chip}$ bus, with the receiving chiplet responding through the main chiplet bus $b_{chip}$. + +At the moment, this feature is only used by the [ACE](./ace.md) allowing it to read inputs and circuit instructions stored in the memory chiplet. +Note that the [memory](./memory.md#chiplets-bus-constraints) chiplet responds via the chiplet bus $b_{chip}$. + +To combine these correctly, the [running product column](../lookups/multiset.md) for this table must be constrained not only at the beginning and the end of the trace, but also where the hash chiplet ends. + +### Chiplets virtual table constraints + +Although the [hasher chiplet](./hasher.md#sibling-table-constraints) ensures the sibling table is empty between any Merkle tree update computation, +we must also enforce this property at the boundary of the chiplet itself. +Using the hasher chiplet's selector $s_0$, the following constraint ensures the bus equals one whenever $s_0$ transitions. + +> $$ +> (s'_0 - s_0) \cdot (1 - vt_{chip}) = 0 \text{ | degree} = 2 +> $$ + +To connect the chiplet virtual table and the bus, we enforce the following constraint in the last row, ensuring the product of both their running products is 1. + +> $$ +> vt_{chip} \cdot b_{chip} - 1 = 0 \text{ | degree} = 2. +> $$ + +## Chiplet logUp bus + +An auxiliary [logUp bus](../lookups/logup.md) is available to chiplets, though it is currently only used by the [ACE chiplet](./ace.md#wire-bus) to check the wiring of the arithmetic circuit being evaluated. We refer to that chiplet's documentation for constraint applied to the corresponding auxiliary column. + +Since this column could later be used by other chiplets, we require boundary constraints over the entire chiplet trace to constrain the running sum to be zero in the first and final row of the auxiliary column. diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/design/chiplets/kernel_rom.md b/versioned_docs/version-0.12 (stable)/miden-vm/design/chiplets/kernel_rom.md new file mode 100644 index 0000000..4103434 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-vm/design/chiplets/kernel_rom.md @@ -0,0 +1,119 @@ +--- +title: "Kernel ROM Chiplet" +sidebar_position: 6 +--- + +# Kernel ROM chiplet + +The kernel ROM enables executing predefined kernel procedures. +These procedures are always executed in the root context and can only be accessed by a `SYSCALL` operation. +The chiplet tracks and enforces correctness of all kernel procedure calls as well as maintaining a list of all the procedures defined for the kernel, whether they are executed or not. +More background about Miden VM execution contexts can be found [here](../../user_docs/assembly/execution_contexts.md). + +## Kernel ROM trace + +The kernel ROM table consists of five columns. +The following example table shows the execution trace of the kernel ROM with procedure digests $a, b, c$, which were called 1, 2, and 0 times, respectively. +Each digest is included once to respond to the initialization request by the public inputs, and then repeated for each call made by the decoder. + +| $s_{first}$ | $r_0$ | $r_1$ | $r_2$ | $r_3$ | +|-------------|-------|-------|-------|-------| +| 1 | $a_0$ | $a_1$ | $a_2$ | $a_3$ | +| 0 | $a_0$ | $a_1$ | $a_2$ | $a_3$ | +| 1 | $b_0$ | $b_1$ | $b_2$ | $b_3$ | +| 0 | $b_0$ | $b_1$ | $b_2$ | $b_3$ | +| 0 | $b_0$ | $b_1$ | $b_2$ | $b_3$ | +| 1 | $c_0$ | $c_1$ | $c_2$ | $c_3$ | + +The meaning of columns in the above is as follows: + +- Column $s_{first}$ specifies the start of a block of rows with identical kernel procedure digests. +- $r_0, ..., r_3$ contain the digests of the kernel procedures. The values in these columns can change only when $s_{first}$ is set to 1 in the next row. Otherwise, the values in the $r$ columns remain the same. + +## Constraints + +> Note: the following assumes the ACE chiplet is included in the previous slot, whose documentation will be included +> in a subsequent PR. + +The following constraints are required to enforce the correctness of the kernel ROM trace. + +_Note: Unless otherwise stated, these constraints should also be multiplied by chiplets module's virtual flag $f_{krom}$ which is active in all rows the kernel ROM chiplet._ + +The $s_{first}$ column is a selector indicating the start of a new digest included in the kernel ROM chiplet trace. +In this row, the chiplet responds to a bus request made by the verifier to ensure consistency with the set of kernel procedure digests given as public inputs. + +As $s_{first}$ is a selector, it must be binary. + +> $$ +> s_{first}^2 - s_{first} = 0 \text{ | degree} = 2 +> $$ + + +The flag $s_{first}$ must be set to be 1 in the first row of the kernel ROM chiplet. +Otherwise, the digest in this row would not be matched with one of the input procedure roots. +This constraint is enforced in the last row of the previous trace, using selector columns from the [chiplets](index.md) module. +More precisely, we use the virtual $f_{ACE}$ flag from the chiplet selectors $s_0, s_1, \ldots, s_{ACE}$ which is active in all rows of the previous (in this case ACE) chiplet, +along with the selector $s_{ACE}$ which transitions from 0 to 1 in the last row, allowing us to target the first row of the kernel ROM trace. + +> $$ +> f_{ACE} \cdot s_{ACE}' \cdot (1 - s_{first}') = 0 \text{ | degree} = \deg(f_{prev}) + 2 +> $$ + +_Note that this selector need not be multiplied by the kernel ROM chiplet flag $chip\_s_4$, since it is only active when the previous chiplet is active._ + +The contiguity of the digests in a block is ensured by enforcing equality between digests across two consecutive rows, whenever the next row is not the start of a new block. +That is, when $s_{first}' = 0$, it must hold that $r_i = r_i'$. +We disable this constraint in the last row of the kernel ROM chiplet trace by using the kernel ROM chiplet selector $s_4'$, since the latter transitions from 0 to 1 when the next chiplet starts. + +> $$ +> (1 - s_4') \cdot (1 - s_{first}') \cdot (r_i' - r_i) = 0 \text{ | degree} = 3 +> $$ + +_**Note**: we could technically remove the selector $(1-s_4')$ since $s_4$ and $s_{first}$ correspond to the same column. We include it here for completeness though._ + +### Chiplets bus constraints + +The kernel ROM chiplet must ensure that all kernel procedure digests requested by the decoder correspond to one of the digests provided by the verifier through public inputs. +This is achieved by making use of the chiplet bus $b_{bus}$, responding to requests made by the decoder and by the verifier through public inputs. + +In the first row of each new block of hashes in the kernel ROM chiplet trace (i.e., when $s_{first} = 1$), the chiplet responds to a message $v_{init}$ requested by the verifier. +Since these initialization messages must match, the set of digests across all blocks must be equal to the set of procedure digests provided by the verifier (though not necessarily in the same order). + +Whenever a digest is requested by the decoder during program block hashing of the [`SYSCALL` operation](../decoder/constraints.md#block-hash-computation-constraints), a new row is added to the trace after the first row which is used to respond to one of the initialization requests made by the verifier using public inputs. +The chiplet responds to the request with a message $v_{call}$. + +In other words, the selector $s_{first}$ indicates whether the chiplet should respond to the decoder or the verifier initialization requests. +If a digest is requested $n$ times by the decoder, the same digest appears in a single block of length $n+1$. + +The variables $v_{init}$ and $v_{call}$ representing the bus messages contain reduced bus messages containing a kernel procedure digest. +Denoting the random values received from the verifier as $\alpha_0, \alpha_1$, etc., this can be defined as + +$$ +\begin{aligned} +\tilde{r} &= \sum_{i=0}^3 (\alpha_{i + 2} \cdot r_i) +v_{init} &= \alpha_0 + \alpha_1 \cdot \textsf{KERNEL\_PROC\_INIT} + \tilde{r} +v_{call} &= \alpha_0 + \alpha_1 \cdot \textsf{KERNEL\_PROC\_CALL} + \tilde{r} +\end{aligned} +$$ + +Here, $\textsf{KERNEL\_PROC\_INIT}$ and $\textsf{KERNEL\_PROC\_CALL}$ are the unique [operation labels](./index.md#operation-labels) for the kernel ROM bus message. + +Each row of the kernel ROM chiplet trace responds to either a procedure digest initialization or decoder call request. +Since the $s_{first}$ column defines which type of response is sent to the bus, it is used to combine both requests into a single constraint given by + +> $$ +> b'_{chip} = b_{chip} \cdot (s_{first} \cdot v_{init} + (1 - s_{first}) \cdot v_{call}) \text{ | degree} = 3. +> $$ + +The above simplifies to + +- $s_{first} = 1$: $b'_{chip} = b_{chip} \cdot v_{init}$, when responding to a $\textsf{KERNEL\_PROC\_INIT}$ request. +- $s_{first} = 0$: $b'_{chip} = b_{chip} \cdot v_{call}$, when responding to a $\textsf{KERNEL\_PROC\_CALL}$ request. + +The kernel procedure digests initialization requests are implemented by imposing a boundary constraint in the first row of the $b_{chip}$ column. +This is described in the [chiplets bus constraints](../chiplets/index.md#chiplets-bus-constraints). + +By using the bus to initialize the kernel ROM procedure digest in this way, the verifier only learns which procedures can be invoked but doesn't learn how often they were called, if at all. + +The full set of constraints applied to the $b_{chip}$ are described as part of the [chiplets bus constraints](../chiplets/index.md#chiplets-bus-constraints). + diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/design/chiplets/memory.md b/versioned_docs/version-0.12 (stable)/miden-vm/design/chiplets/memory.md new file mode 100644 index 0000000..926501b --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-vm/design/chiplets/memory.md @@ -0,0 +1,359 @@ +--- +title: "Memory Chiplet" +sidebar_position: 4 +--- + +# Memory chiplet + +Miden VM supports linear read-write random access memory. This memory is element-addressable, meaning that a single value is located at each address, although reading and writing values to/from memory in batches of four is supported. Each value is a field element in a $64$-bit prime field with modulus $2^{64} - 2^{32} + 1$. A memory address is a field element in the range $[0, 2^{32})$. + +In this note we describe the rationale for selecting the above design and describe AIR constraints needed to support it. + +The design makes extensive use of $16$-bit range checks. An efficient way of implementing such range checks is described [here](../range.md). + +## Alternative designs + +The simplest (and most efficient) alternative to the above design is contiguous write-once memory. To support such memory, we need to allocate just two trace columns as illustrated below. + +![memory_alternative_design](../../img/design/chiplets/memory/memory_alternative_design.png) + +In the above, `addr` column holds memory address, and `value` column holds the field element representing the value stored at this address. Notice that some rows in this table are duplicated. This is because we need one row per memory access (either read or write operation). In the example above, value $b$ was first stored at memory address $1$, and then read from this address. + +The AIR constraints for this design are very simple. First, we need to ensure that values in the `addr` column either remain the same or are incremented by $1$ as we move from one row to the next. This can be achieved with the following constraint: + +$$ +(a' - a) \cdot (a' - a - 1) = 0 +$$ + +where $a$ is the value in `addr` column in the current row, and $a'$ is the value in this column in the next row. + +Second, we need to make sure that if the value in the `addr` column didn't change, the value in the `value` column also remained the same (i.e., a value stored in a given address can only be set once). This can be achieved with the following constraint: + +$$ +(v' - v) \cdot (a' - a - 1) = 0 +$$ + +where $v$ is the value in `value` column at the current row, and $v'$ is the value in this column in the next row. + +As mentioned above, this approach is very efficient: each memory access requires just $2$ trace cells. + +### Read-write memory + +Write-once memory is tricky to work with, and many developers may need to climb a steep learning curve before they become comfortable working in this model. Thus, ideally, we'd want to support read-write memory. To do this, we need to introduce additional columns as illustrated below. + +![memory_read_write](../../img/design/chiplets/memory/memory_read_write.png) + +In the above, we added `clk` column, which keeps track of the clock cycle at which memory access happened. We also need to differentiate between memory reads and writes. To do this, we now use two columns to keep track of the value: `old val` contains the value stored at the address before the operation, and `new val` contains the value after the operation. Thus, if `old val` and `new val` are the same, it was a read operation. If they are different, it was a write operation. + +The AIR constraints needed to support the above structure are as follows. + +We still need to make sure memory addresses are contiguous: + +$$ +(a' - a) \cdot (a' - a - 1) = 0 +$$ + +Whenever memory address changes, we want to make sure that `old val` is set to $0$ (i.e., our memory is always initialized to $0$). This can be done with the following constraint: + +$$ +(a' - a) \cdot v_{old}' = 0 +$$ + +On the other hand, if memory address doesn't change, we want to make sure that `new val` in the current row is the same as `old val` in the next row. This can be done with the following constraint: + +$$ +(1 + a - a') \cdot (v_{new} - v_{old}') = 0 +$$ + +Lastly, we need to make sure that for the same address values in `clk` column are always increasing. One way to do this is to perform a $16$-bit range check on the value of $(i' - i - 1)$, where $i$ is the reference to `clk` column. However, this would mean that memory operations involving the same address must happen within $65536$ VM cycles from each other. This limitation would be difficult to enforce statically. To remove this limitation, we need to add two more columns as shown below: + +![memory_limitation_diagram](../../img/design/chiplets/memory/memory_limitation_diagram.png) + +In the above column `d0` contains the lower $16$ bits of $(i' - i - 1)$ while `d1` contains the upper $16$ bits. The constraint needed to enforces this is as follows: + +$$ +(1 + a - a') \cdot ((i' - i - 1) - (2^{16} \cdot d_1' + d_0')) = 0 +$$ + +Additionally, we need to apply $16$-bit range checks to columns `d0` and `d1`. + +Overall, the cost of reading or writing a single element is now $6$ trace cells and $2$ $16$-bit range-checks. + +### Non-contiguous memory + +Requiring that memory addresses are contiguous may also be a difficult limitation to impose statically. To remove this limitation, we need to introduce one more column as shown below: + +![memory_non_contiguous_memory](../../img/design/chiplets/memory/memory_non_contiguous_memory.png) + +In the above, the prover sets the value in the new column `t` to $0$ when the address doesn't change, and to $1 / (a' - a)$ otherwise. To simplify constraint description, we'll define variable $n$ computed as follows: + +$$ +n = (a' - a) \cdot t' +$$ + +Then, to make sure the prover sets the value of $t$ correctly, we'll impose the following constraints: + +$$ +n^2 - n = 0 +(1 - n) \cdot (a' - a) = 0 +$$ + +The above constraints ensure that $n=1$ whenever the address changes, and $n=0$ otherwise. We can then define the following constraints to make sure values in columns `d0` and `d1` contain either the delta between addresses or between clock cycles. + +| Condition | Constraint | Comments | +| --------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | +| $n=1$ | $(a' - a) - (2^{16} \cdot d_1' + d_0') = 0$ | When the address changes, columns `d0` and `d1` at the next row should contain the delta between the old and the new address. | +| $n=0$ | $(i' - i - 1) - (2^{16} \cdot d_1' + d_0') = 0$ | When the address remains the same, columns `d0` and `d1` at the next row should contain the delta between the old and the new clock cycle. | + +We can combine the above constraints as follows: + +$$ +\left(n \cdot (a' - a) + (1 - n) \cdot (i' - i - 1)\right) - (2^{16} \cdot d_1' + d_0') = 0 +$$ + +The above constraint, in combination with $16$-bit range checks against columns `d0` and `d1` ensure that values in `addr` and `clk` columns always increase monotonically, and also that column `addr` may contain duplicates, while values in `clk` column must be unique for a given address. + +### Context separation + +In many situations it may be desirable to assign memories to different contexts. For example, when making a cross-contract calls, the memories of the caller and the callee should be separate. That is, the caller should not be able to access the memory of the callee and vice-versa. + +To accommodate this feature, we need to add one more column as illustrated below. + +![memory_context_separation](../../img/design/chiplets/memory/memory_context_separation.png) + +This new column `ctx` should behave similarly to the address column: values in it should increase monotonically, and there could be breaks between them. We also need to change how the prover populates column `t`: + +- If the context changes, `t` should be set to the inverse $(c' - c)$, where $c$ is a reference to column `ctx`. +- If the context remains the same but the address changes, column `t` should be set to the inverse of $(a' - a)$. +- Otherwise, column `t` should be set to $0$. + +To simplify the description of constraints, we'll define two variables $n_0$ and $n_1$ as follows: + +$$ +n_0 = (c' - c) \cdot t' +n_1 = (a' - a) \cdot t' +$$ + +Thus, $n_0 = 1$ when the context changes, and $0$ otherwise. Also, $(1 - n_0) \cdot n_1 = 1$ when context remains the same and address changes, and $0$ otherwise. + +To make sure the prover sets the value of column `t` correctly, we'll need to impose the following constraints: + +$$ +n_0^2 - n_0 = 0 +(1 - n_0) \cdot (c' - c) = 0 +(1 - n_0) \cdot (n_1^2 - n_1) = 0 +(1 - n_0) \cdot (1 - n_1) \cdot (a' - a) = 0 +$$ + +We can then define the following constraints to make sure values in columns `d0` and `d1` contain the delta between contexts, between addresses, or between clock cycles. + +| Condition | Constraint | Comments | +| -------------------- | ----------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| $n_0=1$ | $(c' - c) - (2^{16} \cdot d_1' + d_0') = 0$ | When the context changes, columns `d0` and `d1` at the next row should contain the delta between the old and the new contexts. | +| $n_0=0$
$n_1=1$ | $(a' - a) - (2^{16} \cdot d_1' + d_0') = 0$ | When the context remains the same but the address changes, columns `d0` and `d1` at the next row should contain the delta between the old and the new addresses. | +| $n_0=0$
$n_1=0$ | $(i' - i - 1) - (2^{16} \cdot d_1' + d_0') = 0$ | When both the context and the address remain the same, columns `d0` and `d1` at the next row should contain the delta between the old and the new clock cycle. | + +We can combine the above constraints as follows: + +$$ +\left(n_0 \cdot (c' - c) + (1 - n_0) \cdot \left(n_1 \cdot (a - a') + (1 - n_1) \cdot (i' - i - 1) \right) \right) - (2^{16} \cdot d_1' + d_0') = 0 +$$ + +The above constraint, in combination with $16$-bit range checks against columns `d0` and `d1` ensure that values in `ctx`, `addr`, and `clk` columns always increase monotonically, and also that columns `ctx` and `addr` may contain duplicates, while the values in column `clk` must be unique for a given combination of `ctx` and `addr`. + +Notice that the above constraint has degree $5$. + +## Miden approach + +While the approach described above works, it comes at significant cost. Reading or writing a single value requires $8$ trace cells and $2$ $16$-bit range checks. Assuming a single range check requires roughly $2$ trace cells, the total number of trace cells needed grows to $12$. This is about $6$x worse the simple contiguous write-once memory described earlier. + +Miden VM frequently needs to deal with batches of $4$ field elements, which we call _words_. For example, the output of Rescue Prime Optimized hash function is a single word. A single 256-bit integer value can be stored as two words (where each element contains one $32$-bit value). Thus, we can optimize for this common use case by making the chiplet handle *words* as opposed to individual elements. That is, memory is still element-addressable in that each memory address stores a single field element, and memory addresses may be read or written individually. However, the chiplet also handles reading and writing elements in batches of four simultaneously, with the restriction that such batches be *word-aligned* addresses (*i.e.* the address is a multiple of 4). + +The layout of Miden VM memory table is shown below: + +![memory_miden_vm_layout](../../img/design/chiplets/memory/memory_miden_vm_layout.png) + +where: + +- `rw` is a selector column which is set to $1$ for read operations and $0$ for write operations. +- `ew` is a selector column which is set to $1$ when a word is being accessed, and $0$ when an element is being accessed. +- `ctx` contains context ID. Values in this column must increase monotonically but there can be gaps between two consecutive values of up to $2^{32}$. Also, two consecutive values can be the same. +- `word_addr` contains the memory address of the first element in the word. Values in this column must increase monotonically for a given context but there can be gaps between two consecutive values of up to $2^{32}$. Values in this column must be divisible by 4. Also, two consecutive values can be the same. +- `idx0` and `idx1` are selector columns used to identify which element in the word is being accessed. Specifically, the index within the word is computed as `idx1 * 2 + idx0`. + - However, when `ew` is set to $1$ (indicating that a word is accessed), these columns are meaningless and are set to $0$. +- `clk` contains clock cycle at which the memory operation happened. Values in this column must increase monotonically for a given context and memory word but there can be gaps between two consecutive values of up to $2^{32}$. + - Unlike the previously described approaches, we allow `clk` to be constant in the same context/word address, with the restriction that when this is the case, then only reads are allowed. +- `v0, v1, v2, v3` columns contain field elements stored at a given context/word/clock cycle after the memory operation. +- Columns `d0` and `d1` contain lower and upper $16$ bits of the delta between two consecutive context IDs, addresses, or clock cycles. Specifically: + - When the context changes within a frame, these columns contain $(ctx' - ctx)$ in the "next" row. + - When the context remains the same but the word address changes within a frame, these columns contain $(a' - a)$ in the "next" row. + - When both the context and the word address remain the same within a frame, these columns contain $(clk' - clk)$ in the "next" row. +- Column `t` contains the inverse of the delta between two consecutive context IDs, addresses, or clock cycles. Specifically: + - When the context changes within a frame, this column contains the inverse of $(ctx' - ctx)$ in the "next" row. + - When the context remains the same but the word address changes within a frame, this column contains the inverse of $(a' - a)$ in the "next" row. + - When both the context and the word address remain the same within a frame, this column contains the inverse of $(clk' - clk)$ in the "next" row. +- Column `f_scw` stands for "flag same context and word address", which is set to $1$ when the current and previous rows have the same context and word address, and $0$ otherwise. + +For every memory access operation (i.e., read or write a word or element), a new row is added to the memory table. If neither `ctx` nor `addr` have changed, the `v` columns are set to equal the values from the previous row (except for any element written to). If `ctx` or `addr` have changed, then the `v` columns are initialized to $0$ (except for any element written to). + +### AIR constraints + +We first define the memory chiplet selector flags. $s_0$, $s_1$ and $s_2$ will refer to the chiplet selector flags. + +- $f_{mem}$ is set to 1 when the current row is in the memory chiplet. +$$ +f_{mem} = s_0 \cdot s_1 \cdot (1 - s_2) \text{ | degree} = 3 +$$ + +- $f_{mem\_nl}$ is set to 1 when the current row is in the memory chiplet, except for the last row of the chiplet. + +$$ +f_{mem\_nl} = s_0 \cdot s_1 \cdot (1 - s_2') \text{ | degree} = 3 +$$ + +- $f_{mem\_fr}$ is set to 1 when the next row is the first row of the memory chiplet. + +$$ +f_{mem\_fr} = (1 - s_0) \cdot f_{mem}' \text{ | degree} = 4 +$$ + +To simplify description of constraints, we'll define two variables $n_0$ and $n_1$ as follows: + +$$ +n_0 = \Delta ctx \cdot t' +n_1 = \Delta a \cdot t' +$$ + +Where $\Delta ctx = ctx' - ctx$ and $\Delta a = a' - a$. + +To make sure the prover sets the value of column `t` correctly, we'll need to impose the following constraints: + +$$ +f_{mem\_nl} \cdot (n_0^2 - n_0) = 0 \text{ | degree} = 7 +$$ + +$$ +f_{mem\_nl} \cdot (1 - n_0) \cdot \Delta ctx = 0 \text{ | degree} = 7 +$$ + +$$ +f_{mem\_nl} \cdot (1 - n_0) \cdot (n_1^2 - n_1) = 0 \text{ | degree} = 9 +$$ + +$$ +f_{mem\_nl} \cdot (1 - n_0) \cdot (1 - n_1) \cdot \Delta a = 0 \text{ | degree} = 8 +$$ + +The above constraints guarantee that when context changes, $n_0 = 1$. When context remains the same but word address changes, $(1 - n_0) \cdot n_1 = 1$. And when neither the context nor the word address change, $(1 - n_0) \cdot (1 - n_1) = 1$. + +We enforce that the `rw`, `ew`, `idx0` and `idx1` contain binary values. + +$$ +f_{mem} \cdot (rw^2 - rw) = 0 \text{ | degree} = 5 +$$ + +$$ +f_{mem} \cdot (ew^2 - ew) = 0 \text{ | degree} = 5 +$$ + +$$ +f_{mem} \cdot (idx0^2 - idx0) = 0 \text{ | degree} = 5 +$$ + +$$ +f_{mem} \cdot (idx1^2 - idx1) = 0 \text{ | degree} = 5 +$$ + + +To enforce the values of context ID, word address, and clock cycle grow monotonically as described in the previous section, we define the following constraint. + +$$ +f_{mem\_nl} \cdot \left(n_0 \cdot \Delta ctx + (1 - n_0) \cdot (n_1 \cdot \Delta a + (1 - n_1) \cdot \Delta clk) \right) - (2^{16} \cdot d_1' + d_0') = 0 \text{ | degree} = 8 +$$ + +In addition to this constraint, we also need to make sure that the values in registers $d_0$ and $d_1$ are less than $2^{16}$, and this can be done with [range checks](../range.md). + +Next, we need to ensure that when the context, word address and clock are constant in a frame, then only read operations are allowed in that clock cycle. + +$$ +f_{mem\_nl} \cdot f_{scw}' \cdot (1 - \Delta clk \cdot t') \cdot (1 - rw) \cdot (1 - rw') = 0 \text{ | degree} = 8 +$$ + + +Next, for all frames where the "current" and "next" rows are in the chiplet, we need to ensure that the value of the `f_scw` column in the "next" row is set to $1$ when the context and word address are the same, and $0$ otherwise. + +$$ +f_{mem\_nl} \cdot (f_{scw}' - (1 - n_0) \cdot (1-n_1)) = 0 \text{ | degree} = 7 +$$ + +Note that this does not constrain the value of `f_scw` in the first row of the chiplet. This is intended, as the first row's constraints do not depend on the previous row (since the previous row is not part of the same chiplet), and therefore do not depend on `f_scw` (see "first row" constraints below). + +Finally, we need to constrain the `v0, v1, v2, v3` columns. We will define a few variables to help in defining the constraints. + +$$ +\begin{align*} +f_0 &= (1 - idx1) \cdot (1 - idx0) \text{ | degree} = 2 +f_1 &= (1 - idx1) \cdot idx0 \text{ | degree} = 2 +f_2 &= idx1 \cdot (1 - idx0) \text{ | degree} = 2 +f_3 &= idx1 \cdot idx0 \text{ | degree} = 2 +\end{align*} +$$ + +The flag $f_i$ is set to $1$ when $v_i$ is being accessed, and $0$ otherwise. Next, for $0 \leq i < 4$, + +$$ +c_i = rw' + (1 - rw') \cdot (1 - ew') \cdot (1 - f_i') \text{ | degree} = 4 +$$ + +which is set to $1$ when $v_i$ is *not* written to, and $0$ otherwise. + +We're now ready to describe the constraints for the `v0, v1, v2, v3` columns. + +- For the first row of the chiplet (in the "next" position of the frame), for $0 \leq i < 4$, + +$$ +f_{mem\_fr} \cdot c_i \cdot v_i' = 0 \text{ | degree} = 9 +$$ + +That is, if the next row is the first row of the memory chiplet, and $v_i'$ is not written to, then $v_i'$ must be $0$. + +- For all rows of the chiplet except the first, for $0 \leq i < 4$, + +$$ +f_{mem\_nl} \cdot c_i \cdot (f_{scw}' \cdot (v_i' - v_i) + (1 - f_{scw}') \cdot v_i') = 0 \text{ | degree} = 9 +$$ + +That is, if $v_i$ is not written to, then either its value needs to be copied over from the previous row (when $f_{scw}' = 1$), or it must be set to 0 (when $f_{scw}' = 0$). + +#### Chiplets bus constraints {#chiplets-bus-constraints} + +Communication between the memory chiplet and the stack is accomplished via the chiplets bus $b_{chip}$. To respond to memory access requests from the stack, we need to divide the current value in $b_{chip}$ by the value representing a row in the memory table. + +##### Memory row value {#memory-row-value} + +This value can be computed as follows: + +$$ +\begin{align*} +v_{mem} = \alpha_0 + \alpha_1 \cdot op_{mem} + \alpha_2 \cdot ctx + \alpha_3 \cdot a + \alpha_4 \cdot clk + ew \cdot v_{word} + (1 - ew) \cdot v_{element} \text{ | degree} = 4 +\end{align*} +$$ + +where + +$$ +\begin{align*} +v_{word} &= \sum_{j=0}^3(\alpha_{j + 5} \cdot v_j) \text{ | degree} = 1 +v_{element} &= \alpha_5 \cdot \sum_{i=0}^3 f_i \cdot v_i \text{ | degree} = 3 +\end{align*} +$$ + +and where $op_{mem}$ is the appropriate [operation label](./index.md#operation-labels) of the memory access operation. + +To ensure that values of memory table rows are included into the chiplets bus, we impose the following constraint: + +$$ +b_{chip}' = b_{chip} \cdot v_{mem} \text{ | degree} = 5 +$$ + +On the stack side, for every memory access request, a corresponding value is divided out of the $b_{chip}$ column. Specifics of how this is done are described [here](../stack/io_ops.md#memory-access-operations). diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/design/decoder/_category_.yml b/versioned_docs/version-0.12 (stable)/miden-vm/design/decoder/_category_.yml new file mode 100644 index 0000000..3f761fa --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-vm/design/decoder/_category_.yml @@ -0,0 +1,4 @@ +label: "Program decoder" +# Determines where this documentation section appears relative to other sections in the parent folder +position: 2 +collapsed: true diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/design/decoder/constraints.md b/versioned_docs/version-0.12 (stable)/miden-vm/design/decoder/constraints.md new file mode 100644 index 0000000..fb8e7bb --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-vm/design/decoder/constraints.md @@ -0,0 +1,676 @@ +--- +title: "Miden VM Decoder AIR Constraints" +sidebar_position: 2 +--- + +# Miden VM decoder AIR constraints + +In this section we describe AIR constraints for Miden VM program decoder. These constraints enforce that the execution trace generated by the prover when executing a particular program complies with the rules described in the [previous section](./index.md). + +To refer to decoder execution trace columns, we use the names shown on the diagram below (these are the same names as in the previous section). Additionally, we denote the register containing the value at the top of the stack as $s_0$. + +![air_decoder_columns](../../img/design/decoder/constraints/air_decoder_columns.png) + +We assume that the VM exposes a flag per operation which is set to $1$ when the operation is executed, and to $0$ otherwise. The notation for such flags is $f_{opname}$. For example, when the VM executes a `PUSH` operation, flag $f_{push} = 1$. All flags are mutually exclusive - i.e., when one flag is set to $1$ all other flags are set to $0$. The flags are computed based on values in `op_bits` columns. + +AIR constraints for the decoder involve operations listed in the table below. For each operation we also provide the degree of the corresponding flag and the effect that the operation has on the operand stack (however, in this section we do not cover the constraints needed to enforce the correct transition of the operand stack). + +| Operation | Flag | Degree | Effect on stack | +| --------- | :-----------: |:------:| ------------------------------------------------------------------------------------------------ | +| `JOIN` | $f_{join}$ | 5 | Stack remains unchanged. | +| `SPLIT` | $f_{split}$ | 5 | Top stack element is dropped. | +| `LOOP` | $f_{loop}$ | 5 | Top stack element is dropped. | +| `REPEAT` | $f_{repeat}$ | 4 | Top stack element is dropped. | +| `SPAN` | $f_{span}$ | 5 | Stack remains unchanged. | +| `RESPAN` | $f_{respan}$ | 4 | Stack remains unchanged. | +| `DYN` | $f_{dyn}$ | 5 | Stack remains unchanged. | +| `CALL` | $f_{call}$ | 4 | Stack remains unchanged. | +| `SYSCALL` | $f_{syscall}$ | 4 | Stack remains unchanged. | +| `END` | $f_{end}$ | 4 | When exiting a loop block, top stack element is dropped; otherwise, the stack remains unchanged. | +| `HALT` | $f_{halt}$ | 4 | Stack remains unchanged. | +| `PUSH` | $f_{push}$ | 5 | An immediate value is pushed onto the stack. | +| `EMIT` | $f_{emit}$ | 7 | Stack remains unchanged. | + +We also use the [control flow flag](../stack/op_constraints.md#control-flow-flag) $f_{ctrl}$ exposed by the VM, which is set when any one of the above control flow operations is being executed. It has degree $5$. + +As described [previously](./index.md#program-decoding), the general idea of the decoder is that the prover provides the program to the VM by populating some of cells in the trace non-deterministically. Values in these are then used to update virtual tables (represented via multiset checks) such as block hash table, block stack table etc. Transition constraints are used to ensure that the tables are updates correctly, and we also apply boundary constraints to enforce the correct initial and final states of these tables. One of these boundary constraints binds the execution trace to the hash of the program being executed. Thus, if the virtual tables were updated correctly and boundary constraints hold, we can be convinced that the prover executed the claimed program on the VM. + +In the sections below, we describe constraints according to their logical grouping. However, we start out with a set of general constraints which are applicable to multiple parts of the decoder. + +## General constraints + +When `SPLIT` or `LOOP` operation is executed, the top of the operand stack must contain a binary value: + +> $$ +> (f_{split} + f_{loop}) \cdot (s_0^2 - s_0) = 0 \text{ | degree} = 7 +> $$ + +When a `DYN` operation is executed, the hasher registers must all be set to $0$: + +> $$ +> f_{dyn} \cdot (1 - h_i) = 0 \text { for } i \in [0, 8) \text{ | degree} = 6 +> $$ + +When `REPEAT` operation is executed, the value at the top of the operand stack must be $1$: + +> $$ +> f_{repeat} \cdot (1 - s_0) = 0 \text{ | degree} = 5 +> $$ + +Also, when `REPEAT` operation is executed, the value in $h_4$ column (the `is_loop_body` flag), must be set to $1$. This ensures that `REPEAT` operation can be executed only inside a loop: + +> $$ +> f_{repeat} \cdot (1 - h_4) = 0 \text{ | degree} = 5 +> $$ + +When `RESPAN` operation is executed, we need to make sure that the block ID is incremented by $8$: + +> $$ +> f_{respan} \cdot (a' - a - 8) = 0 \text{ | degree} = 5 +> $$ + +When `END` operation is executed and we are exiting a *loop* block (i.e., `is_loop`, value which is stored in $h_5$, is $1$), the value at the top of the operand stack must be $0$: + +> $$ +> f_{end} \cdot h_5 \cdot s_0 = 0 \text{ | degree} = 6 +> $$ + +Also, when `END` operation is executed and the next operation is `REPEAT`, values in $h_0, ..., h_4$ (the hash of the current block and the `is_loop_body` flag) must be copied to the next row: + +> $$ +> f_{end} \cdot f_{repeat}' \cdot (h_i' - h_i) = 0 \text { for } i \in [0, 5) \text{ | degree} = 9 +> $$ + +A `HALT` instruction can be followed only by another `HALT` instruction: + +> $$ +> f_{halt} \cdot (1 - f_{halt}') = 0 \text{ | degree} = 8 +> $$ + +When a `HALT` operation is executed, block address column must be $0$: + +> $$ +> f_{halt} \cdot a = 0 \text{ | degree} = 5 +> $$ + +Values in `op_bits` columns must be binary (i.e., either $1$ or $0$): + +> $$ +> b_i^2 - b_i = 0 \text{ for } i \in [0, 7) \text{ | degree} = 2 +> $$ + +When the value in `in_span` column is set to $1$, control flow operations cannot be executed on the VM, but when `in_span` flag is $0$, only control flow operations can be executed on the VM: + +> $$ +> 1 - sp - f_{ctrl} = 0 \text{ | degree} = 5 +> $$ + +## Block hash computation constraints +As described [previously](./index.md#program-block-hashing), when the VM starts executing a new block, it also initiates computation of the block's hash. There are two separate methodologies for computing block hashes. + +For *join*, *split*, and *loop* blocks, the hash is computed directly from the hashes of the block's children. The prover provides these child hashes non-deterministically by populating registers $h_0,..., h_7$. For *dyn*, the hasher registers are populated with zeros, so the resulting hash is a constant value. The hasher is initialized using the hash chiplet, and we use the address of the hasher as the block's ID. The result of the hash is available $7$ rows down in the hasher table (i.e., at row with index equal to block ID plus $7$). We read the result from the hasher table at the time the `END` operation is executed for a given block. + +For *basic* blocks, the hash is computed by absorbing a linear sequence of instructions (organized into operation groups and batches) into the hasher and then returning the result. The prover provides operation batches non-deterministically by populating registers $h_0, ..., h_7$. Similarly to other blocks, the hasher is initialized using the hash chiplet at the start of the block, and we use the address of the hasher as the ID of the first operation batch in the block. As we absorb additional operation batches into the hasher (by executing `RESPAN` operation), the batch address is incremented by $8$. This moves the "pointer" into the hasher table $8$ rows down with every new batch. We read the result from the hasher table at the time the `END` operation is executed for a given block. + +### Chiplets bus constraints + +The decoder communicates with the hash chiplet via the [chiplets bus](../chiplets/index.md#chiplets-bus). This works by dividing values of the multiset check column $b_{chip}$ by the values of operations providing inputs to or reading outputs from the hash chiplet. A constraint to enforce this would look as $b_{chip}' \cdot u = b_{chip}$, where $u$ is the value which defines the operation. + +In constructing value of $u$ for decoder AIR constraints, we will use the following labels (see [here](../chiplets/hasher.md#multiset-check-constraints) for an explanation of how values for these labels are computed): + +* $m_{bp}$ this label specifies that we are starting a new hash computation. +* $m_{abp}$ this label specifies that we are absorbing the next sequence of $8$ elements into an ongoing hash computation. +* $m_{hout}$ this label specifies that we are reading the result of a hash computation. + +To simplify constraint description, we define the following variables: + +$$ +h_{init} = \alpha_0 + \alpha_1 \cdot m_{bp} + \alpha_2 \cdot a' + \sum_{i=0}^7(\alpha_{i + 8} \cdot h_i) +$$ + +In the above, $h_{init}$ can be thought of as initiating a hasher with address $a'$ and absorbing $8$ elements from the hasher state ($h_0, ..., h_7$) into it. Control blocks are always padded to fill the hasher rate and as such the $\alpha_4$ (first capacity register) term is set to $0$. + +$$ +h_{abp} = \alpha_0 + \alpha_1 \cdot m_{abp} + \alpha_2 \cdot a' + \sum_{i=0}^7(\alpha_{i + 8} \cdot h_i) +$$ + +It should be noted that $a$ refers to a column in the decoder, as depicted. The addresses in this column are set using the address from the hasher chiplet for the corresponding hash initialization / absorption / return. In the case of $h_{abp}$ the value of the address in column $a$ in the current row of the decoder is set to equal the value of the address of the row in the hasher chiplet where the previous absorption (or initialization) occurred. $a'$ is the address of the next row of the decoder, which is set to equal the address in the hasher chiplet where the absorption referred to by the $h_{abp}$ label is happening. + +$$ +h_{res} = \alpha_0 + \alpha_1 \cdot m_{hout} + \alpha_2 \cdot (a + 7) + \sum_{i=0}^3(\alpha_{i + 8} \cdot h_i) +$$ + +In the above, $a$ represents the address value in the decoder which corresponds to the hasher chiplet address at which the hasher was initialized (or the last absorption took place). As such, $a + 7$ corresponds to the hasher chiplet address at which the result is returned. + +$$ +f_{ctrli} = f_{join} + f_{split} + f_{loop} \text{ | degree} = 5 +$$ + +In the above, $f_{ctrli}$ is set to $1$ when a control flow operation that signifies the initialization of a control block is being executed on the VM (only those control blocks that don't do any concurrent requests to the chiplets bus). Otherwise, it is set to $0$. An exception is made for the `DYN`, `DYNCALL`, `CALL` and `SYSCALL` operations, since although they initialize a control block, they also run another concurrent bus request, and so are handled separately. + +$$ +d = \sum_{b=0}^6(b_i \cdot 2^i) +$$ + +In the above, $d$ represents the opcode value of the opcode being executed on the virtual machine. It is calculated via a bitwise combination of the op bits. We leverage the opcode value to achieve domain separation when hashing control blocks. This is done by populating the second capacity register of the hasher with the value $d$ via the $\alpha_5$ term when initializing the hasher. + +Using the above variables, we define operation values as described below. + +When a control block initializer operation (`JOIN`, `SPLIT`, `LOOP`) is executed, a new hasher is initialized and the contents of $h_0, ..., h_7$ are absorbed into the hasher. As mentioned above, the opcode value $d$ is populated in the second capacity register via the $\alpha_5$ term. + +$$ +u_{ctrli} = f_{ctrli} \cdot (h_{init} + \alpha_5 \cdot d) \text{ | degree} = 6 +$$ + +As mentioned previously, the value sent by the `SYSCALL` operation is defined separately, since in addition to communicating with the hash chiplet it must also send a kernel procedure access request to the kernel ROM chiplet. This value of this kernel procedure request is described by $k_{proc}$. + +$$ +k_{proc} = \alpha_6 + \alpha_7 \cdot op_{krom} + \sum_{i=0}^3 (\alpha_{i + 8} \cdot h_i) +$$ + +In the above, $op_{krom}$ is the unique [operation label](../chiplets/index.md#operation-labels) of the kernel procedure call operation. The values $h_0, h_1, h_2, h_3$ contain the root hash of the procedure being called, which is the procedure that must be requested from the kernel ROM chiplet. + +$$ +u_{syscall} = f_{syscall} \cdot (h_{init} + \alpha_5 \cdot d) \cdot k_{proc} \text{ | degree} = 6 +$$ + +The above value sends both the hash initialization request and the kernel procedure access request to the chiplets bus when the `SYSCALL` operation is executed. + +Similar to `SYSCALL`, `CALL` is handled separately, since in addition to communicating with the hash chiplet, it must also initialize the frame memory pointer (stored in memory at constant address `fmpaddr` with constant value `fmpinit`): + +$$ +m_{fmpwrite} = \alpha_0 + \alpha_1 \cdot m_{writeele} + \alpha_2 \cdot ctx' + \alpha_3 \cdot fmpaddr + \alpha_4 \cdot clk + alpha_5 \cdot fmpinit +$$ + +In the above, $m_{fmpwrite}$ represents a "write element" memory request equivalent to `mem[fmpaddr] = fmpinit` (in pseudo-code) in the new memory context (*i.e.* the memory context of the callee). Currently $fmpaddr = 2^{32} - 1$, and $fmpinit = 2^{31}$. + +$$ +u_{call} = f_{call} \cdot (h_{init} + \alpha_5 \cdot d) \cdot m_{fmpwrite} \text{ | degree} = 6 +$$ + +Similar to `SYSCALL` and `CALL`, `DYN` and `DYNCALL` are handled separately, since in addition to communicating with the hash chiplet they must also issue a memory read operation for the hash of the procedure being called. + +$$ +h_{dynordyncall} = \alpha_0 + \alpha_1 \cdot m_{bp} + \alpha_2 \cdot a' +$$ + +$$ +m_{dynordyncall} = \alpha_0 + \alpha_1 \cdot m_{read} + \alpha_2 \cdot ctx + \alpha_3 \cdot s_0 + \alpha_4 \cdot clk + <[\alpha_5 \dots \alpha_8], h[0 \dots 4]> +$$ + +$$ +u_{dyn} = f_{dyn} \cdot h_{dynordyncall} \cdot m_{dynordyncall} \text{ | degree} = 7 +$$ +$$ +u_{dyncall} = f_{dyncall} \cdot h_{dynordyncall} \cdot m_{dynordyncall} \cdot m_{fmpwrite} \text{ | degree} = 8 +$$ + +In the above, $h_{dynordyncall}$ can be thought of as $h_{init}$, but where the values used for the hasher decoder trace registers is all 0's. $m_{dynordyncall}$ represents a memory read request from memory address $s_0$ (the top stack element), where the result is placed in the first half of the decoder hasher trace, and where $m_{read}$ is a label that represents a memory element read request. Note that similar to `CALL`, `DYNCALL` also creates a new memory context, and hence must also initialize the `fmp`. + +When `SPAN` operation is executed, a new hasher is initialized and contents of $h_0, ..., h_7$ are absorbed into the hasher. The number of operation groups to be hashed is padded to a multiple of the rate width ($8$) and so the $\alpha_4$ is set to 0: + +$$ +u_{span} = f_{span} \cdot h_{init} \text{ | degree} = 6 +$$ + +When `RESPAN` operation is executed, contents of $h_0, ..., h_7$ (which contain the new operation batch) are absorbed into the hasher: + +$$ +u_{respan} = f_{respan} \cdot h_{abp} \text{ | degree} = 5 +$$ + +When `END` operation is executed, the hash result is copied into registers $h_0, .., h_3$: + +$$ +u_{end} = f_{end} \cdot h_{res} \text{ | degree} = 5 +$$ + +Using the above definitions, we can describe the constraint for computing block hashes as follows: + +> $$ +> b_{chip}' \cdot (u_{ctrli} + u_{syscall} + u_{dynordyncall} + u_{span} + u_{respan} + u_{end} + \\ +> 1 - (f_{ctrli} + f_{syscall} + f_{dyn} + f_{dyncall} + f_{span} + f_{respan} + f_{end})) = b_{chip} +> $$ + +We need to add $1$ and subtract the sum of the relevant operation flags to ensure that when none of the flags is set to $1$, the above constraint reduces to $b_{chip}' = b_{chip}$. + +The degree of this constraint is $9$. + +## Block stack table constraints +As described [previously](./index.md#block-stack-table), block stack table keeps track of program blocks currently executing on the VM. Thus, whenever the VM starts executing a new block, an entry for this block is added to the block stack table. And when execution of a block completes, it is removed from the block stack table. + +Adding and removing entries to/from the block stack table is accomplished as follows: +* To add an entry, we multiply the value in column $p_1$ by a value representing a tuple `(blk, prnt, is_loop, ctx_next, b0_next, b1_next, fn_hash_next)` +. A constraint to enforce this would look as $p_1' = p_1 \cdot v$, where $v$ is the value representing the row to be added. +* To remove an entry, we divide the value in column $p_1$ by a value representing a tuple `(blk, prnt, is_loop, ctx_next, b0_next, b1_next, fn_hash_next)`. A constraint to enforce this would look as $p_1' \cdot u = p_1$, where $u$ is the value representing the row to be removed. + +> Recall that the columns `ctx_next, b0_next, b1_next, fn_hash_next` are only set on `CALL`, `SYSCALL`, and their corresponding `END` block. Therefore, for simplicity, we will ignore them when documenting all other block types (such that their values are set to `0`). + +Before describing the constraints for the block stack table, we first describe how we compute the values to be added and removed from the table for each operation. In the below, for block start operations (`JOIN`, `SPLIT`, `LOOP`, `SPAN`) $a$ refers to the ID of the parent block, and $a'$ refers to the ID of the starting block. For `END` operation, the situation is reversed: $a$ is the ID of the ending block, and $a'$ is the ID of the parent block. For `RESPAN` operation, $a$ refers to the ID of the current operation batch, $a'$ refers to the ID of the next batch, and the parent ID for both batches is set by the prover non-deterministically in register $h_1$. + +When `JOIN` operation is executed, row $(a', a, 0)$ is added to the block stack table: + +$$ +v_{join} = f_{join} \cdot (\alpha_0 + \alpha_1 \cdot a' + \alpha_2 \cdot a) \text{ | degree} = 6 +$$ + +When `SPLIT` operation is executed, row $(a', a, 0)$ is added to the block stack table: + +$$ +v_{split} = f_{split} \cdot (\alpha_0 + \alpha_1 \cdot a' + \alpha_2 \cdot a) \text{ | degree} = 6 +$$ + +When `LOOP` operation is executed, row $(a', a, 1)$ is added to the block stack table if the value at the top of the operand stack is $1$, and row $(a', a, 0)$ is added to the block stack table if the value at the top of the operand stack is $0$: + +$$ +v_{loop} = f_{loop} \cdot (\alpha_0 + \alpha_1 \cdot a' + \alpha_2 \cdot a + \alpha_3 \cdot s_0) \text{ | degree} = 6 +$$ + +When `SPAN` operation is executed, row $(a', a, 0)$ is added to the block stack table: + +$$ +v_{span} = f_{span} \cdot (\alpha_0 + \alpha_1 \cdot a' + \alpha_2 \cdot a) \text{ | degree} = 6 +$$ + +When `RESPAN` operation is executed, row $(a, h_1', 0)$ is removed from the block stack table, and row $(a', h_1', 0)$ is added to the table. The prover sets the value of register $h_1$ at the next row to the ID of the parent block: + +$$ +u_{respan} = f_{respan} \cdot (\alpha_0 + \alpha_1 \cdot a + \alpha_2 \cdot h_1') \text{ | degree} = 5 +v_{respan} = f_{respan} \cdot (\alpha_0 + \alpha_1 \cdot a' + \alpha_2 \cdot h_1') \text{ | degree} = 5 +$$ + +When a `DYN` operation is executed, row $(a', a, 0)$ is added to the block stack table: + +$$ +v_{dyn} = f_{dyn} \cdot (\alpha_0 + \alpha_1 \cdot a' + \alpha_2 \cdot a) \text{ | degree} = 6 +$$ + +When a `DYNCALL` operation is executed, row $(a', a, 0, ctx, b_0, b_1, \mathrm{fnhash}[0..3])$ is added to the block stack table: + +$$ +\begin{align*} +v_{dyncall} &= f_{dyncall} \cdot (\alpha_0 + \alpha_1 \cdot a + \alpha_2 \cdot a' + \alpha_4 \cdot ctx +&+ \alpha_5 \cdot b_0 + \alpha_6 \cdot b_1 + <[\alpha_7, \alpha_{10}], \mathrm{fnhash}[0..3]>) \text{ | degree} = 6 +\end{align*} +$$ + +When a `CALL` or `SYSCALL` operation is executed, row $(a', a, 0, ctx, b_0, b_1, \mathrm{fnhash}[0..3])$ is added to the block stack table: + +$$ +\begin{align*} +v_{callorsyscall} &= (f_{call} + f_{syscall}) \cdot (\alpha_0 + \alpha_1 \cdot a + \alpha_2 \cdot a' + \alpha_4 \cdot ctx +&+ \alpha_5 \cdot b_0 + \alpha_6 \cdot b_1 + <[\alpha_7, \alpha_{10}], \mathrm{fnhash}[0..3]>) \text{ | degree} = 5 +\end{align*} +$$ + +When `END` operation is executed, how we construct the row will depend on whether the `IS_CALL` or `IS_SYSCALL` values are set (stored in registers $h_6$ and $h_7$ respectively). If they are not set, then row $(a, a', h_5)$ is removed from the block span table (where $h_5$ contains the `is_loop` flag); otherwise, row $(a ,a', 0, ctx', b_0', b_1', \mathrm{fnhash}'[0..3])$. + +$$ +\begin{align*} +u_{endnocall} &=\alpha_0 + \alpha_1 \cdot a + \alpha_2 \cdot a' +u_{endcall} &= u_{endnocall} + \alpha_4 \cdot ctx' + \alpha_5 \cdot b_0' + \alpha_6 \cdot b_1' + <[\alpha_7, \alpha_{10}], \mathrm{fnhash}'[0..3]> +u_{end} &= f_{end} \cdot ((1 - h_6 - h_7) \cdot u_{endnocall} + (h_6 + h_7) \cdot u_{endcall} ) \text{ | degree} = 6 +\end{align*} +$$ + +Using the above definitions, we can describe the constraint for updating the block stack table as follows: + +> $$ +> p_1' \cdot (u_{end} + u_{respan} + 1 - (f_{end} + f_{respan})) = p_1 \cdot +> (v_{join} + v_{split} + v_{loop} + v_{span} + v_{respan} + v_{dyn} + v_{dyncall} + v_{callorsyscall} + 1 - +> (f_{join} + f_{split} + f_{loop} + f_{span} + f_{respan} + f_{dyn} + f_{dyncall} + f_{call} + f_{syscall})) +> $$ + +We need to add $1$ and subtract the sum of the relevant operation flags from each side to ensure that when none of the flags is set to $1$, the above constraint reduces to $p_1' = p_1$. + +The degree of this constraint is $7$. + +In addition to the above transition constraint, we also need to impose boundary constraints against the $p_1$ column to make sure the first and the last values in the column are set to $1$. This enforces that the block stack table starts and ends in an empty state. + +## Block hash table constraints +As described [previously](./index.md#block-hash-table), when the VM starts executing a new program block, it adds hashes of the block's children to the block hash table. And when the VM finishes executing a block, it removes the block's hash from the block hash table. This means that the block hash table gets updated when we execute the `JOIN`, `SPLIT`, `LOOP`, `REPEAT`, `DYN`, and `END` operations (executing `SPAN` operation does not affect the block hash table because a *basic* block has no children). + +Adding and removing entries to/from the block hash table is accomplished as follows: +* To add an entry, we multiply the value in column $p_2$ by a value representing a tuple `(prnt_id, block_hash, is_first_child, is_loop_body)`. A constraint to enforce this would look as $p_2' = p_2 \cdot v$, where $v$ is the value representing the row to be added. +* To remove an entry, we divide the value in column $p_2$ by a value representing a tuple `(prnt_id, block_hash, is_first_child, is_loop_body)`. A constraint to enforce this would look as $p_2' \cdot u = p_2$, where $u$ is the value representing the row to be removed. + +To simplify constraint descriptions, we define values representing left and right children of a block as follows: + +$$ +ch_1 = \alpha_0 + \alpha_1 \cdot a' + \sum_{i=0}^3(\alpha_{i+2} \cdot h_i) \text{ | degree} = 1 +ch_2 = \alpha_0 + \alpha_1 \cdot a' + \sum_{i=0}^3(\alpha_{i+2} \cdot h_{i+4}) \text{ | degree} = 1 +$$ + +Graphically, this looks like so: + +![air_decoder_left_right_child](../../img/design/decoder/constraints/air_decoder_left_right_child.png) + +In a similar manner, we define a value representing the result of hash computation as follows: + +$$ +bh = \alpha_0 + \alpha_1 \cdot a' + \sum_{i=0}^3(\alpha_{i+2} \cdot h_i) + \alpha_7 \cdot f_{is\_loop\_body} \text{ | degree} = 1 +$$ + +Above, $f_{is\_loop\_body}$ refers to the value in the `IS_LOOP_BODY` column (already constrained to be 0 or 1), located in $h_4$. Also, note that we are not adding a flag indicating whether the block is the first child of a join block (i.e., $\alpha_6$ term is missing). It will be added later on. + +Using the above variables, we define row values to be added to and removed from the block hash table as follows. + +When `JOIN` operation is executed, hashes of both child nodes are added to the block hash table. We add $\alpha_6$ term to the first child value to differentiate it from the second child (i.e., this sets `is_first_child` to $1$): + +$$ +v_{join} = f_{join} \cdot (ch_1 + \alpha_6) \cdot ch_2 \text{ | degree} = 7 +$$ + +When `SPLIT` operation is executed and the top of the stack is $1$, hash of the *true* branch is added to the block hash table, but when the top of the stack is $0$, hash of the *false* branch is added to the block hash table: + +$$ +v_{split} = f_{split} \cdot (s_0 \cdot ch_1 + (1 - s_0) \cdot ch_2) \text{ | degree} = 7 +$$ + +When `LOOP` operation is executed and the top of the stack is $1$, hash of loop body is added to the block hash table. We add $\alpha_7$ term to indicate that the child is a body of a loop. The below also means that if the top of the stack is $0$, nothing is added to the block hash table as the expression evaluates to $0$: + +$$ +v_{loop} = f_{loop} \cdot s_0 \cdot (ch_1 + \alpha_7) \text{ | degree} = 7 +$$ + +When `REPEAT` operation is executed, hash of loop body is added to the block hash table. We add $\alpha_7$ term to indicate that the child is a body of a loop: + +$$v_{repeat} = f_{repeat} \cdot (ch_1 + \alpha_7) \text{ | } \text{degree} = 5$$ + +When `DYN`, `DYNCALL`, `CALL` or `SYSCALL` operation is executed, the hash of the child is added to the block hash table. In all cases, this child is found in the first half of the decoder hasher state. + +$$ +v_{allcalls} = (f_{dyn} + f_{dyncall} + f_{call} + f_{syscall}) \cdot ch_1 \text{ | degree} = 6 +$$ + +When `END` operation is executed, hash of the completed block is removed from the block hash table. However, we also need to differentiate between removing the first and the second child of a *join* block. We do this by looking at the next operation. Specifically, if the next operation is neither `END` nor `REPEAT` nor `HALT`, we know that another block is about to be executed, and thus, we have just finished executing the first child of a *join* block. Thus, if the next operation is neither `END` nor `REPEAT` nor `HALT` we need to set the term for $\alpha_6$ coefficient to $1$ as shown below: + +$$ +u_{end} = f_{end} \cdot (bh + \alpha_6 \cdot (1 - (f_{end}' + f_{repeat}' + f_{halt}'))) \text{ | } \text{degree} = 8 +$$ + +Using the above definitions, we can describe the constraint for updating the block hash table as follows: + +> $$ +> p_2' \cdot (u_{end} + 1 - f_{end}) = +> p_2 \cdot (v_{join} + v_{split} + v_{loop} + v_{repeat} + v_{allcalls} + 1 - (f_{join} + f_{split} + f_{loop} + f_{repeat} + f_{dyn} + f_{dyncall} + f_{call} + f_{syscall})) +> $$ + +We need to add $1$ and subtract the sum of the relevant operation flags from each side to ensure that when none of the flags is set to $1$, the above constraint reduces to $p_2' = p_2$. + +The degree of this constraint is $9$. + +In addition to the above transition constraint, we also need to set the following boundary constraints against the $p_2$ column: +* The first value in the column represents a row for the entire program. Specifically, the row tuple would be `(0, program_hash, 0, 0)`. This row should be removed from the table when the last `END` operation is executed. +* The last value in the column is $1$ - i.e., the block hash table is empty. + +## Basic block +Basic block constraints ensure proper decoding of basic blocks. In addition to the block stack table constraints and block hash table constraints described previously, decoding of basic blocks requires constraints described below. + +### In-span column constraints +The `in_span` column (denoted as $sp$) is used to identify rows which execute non-control flow operations. The values in this column are set as follows: + +* Executing a `SPAN` operation sets the value of `in_span` column to $1$. +* The value remains $1$ until the `END` operation is executed. +* If `RESPAN` operation is executed between `SPAN` and `END` operations, in the row at which `RESPAN` operation is executed `in_span` is set to $0$. It is then reset to $1$ in the following row. +* In all other cases, value in the `in_span` column should be $0$. + +The picture below illustrates the above rules. + +![air_decoder_in_spans_column_constraint](../../img/design/decoder/constraints/air_decoder_in_spans_column_constraint.png) + +To enforce the above rules we need the following constraints. + +When executing `SPAN` or `RESPAN` operation, the next value in $sp$ column must be set to $1$: + +> $$ +> (f_{span} + f_{respan}) \cdot (1 - sp') = 0 \text{ | degree} = 6 +> $$ + +When the next operation is `END` or `RESPAN`, the next value in $sp$ column must be set $0$. + +> $$ +> (f_{end}' + f_{respan}') \cdot sp' = 0 \text{ | degree} = 5 +> $$ + +In all other cases, the value in $sp$ column must be copied over to the next row: + +> $$ +> (1 - f_{span} - f_{respan} - f_{end}' - f_{respan}') \cdot (sp' - sp) = 0 \text{ | degree} = 6 +> $$ + +Additionally, we will need to impose a boundary constraint which specifies that the first value in $sp = 0$. Note, however, that we do not need to impose a constraint ensuring that values in $sp$ are binary - this will follow naturally from the above constraints. + +Also, note that the combination of the above constraints makes it impossible to execute `END` or `RESPAN` operations right after `SPAN` or `RESPAN` operations. + +### Block address constraints +When we are inside a *basic* block, values in block address columns (denoted as $a$) must remain the same. This can be enforced with the following constraint: + +> $$ +> sp \cdot (a' - a) = 0 \text{ | degree} = 2 +> $$ + +Notice that this constraint does not apply when we execute any of the control flow operations. For such operations, the prover sets the value of the $a$ column non-deterministically, except for the `RESPAN` operation. For the `RESPAN` operation the value in the $a$ column is incremented by $8$, which is enforced by a constraint described previously. + +Notice also that this constraint implies that when the next operation is the `END` operation, the value in the $a$ column must also be copied over to the next row. This is exactly the behavior we want to enforce so that when the `END` operation is executed, the block address is set to the address of the current span batch. + +### Group count constraints +The `group_count` column (denoted as $gc$) is used to keep track of the number of operation groups which remains to be executed in a basic block. + +In the beginning of a basic block (i.e., when `SPAN` operation is executed), the prover sets the value of $gc$ non-deterministically. This value is subsequently decremented according to the rules described below. By the time we exit the basic block (i.e., when `END` operation is executed), the value in $gc$ must be $0$. + +The rules for decrementing values in the $gc$ column are as follows: +* The count cannot be decremented by more than $1$ in a single row. +* When an operation group is fully executed (which happens when $h_0 = 0$ inside a basic block), the count is decremented by $1$. +* When `SPAN`, `RESPAN`, or `PUSH` operations are executed, the count is decremented by $1$. + +Note that these rules imply that the `PUSH` operation (or any operation with an immediate value) cannot be the last operation in an operation group (otherwise the count would have to be decremented by $2$). + +To simplify the description of the constraints, we will define the following variable: + +$$ +\Delta gc = gc - gc' +$$ + +Using this variable, we can describe the constraints against the $gc$ column as follows: + +Inside a *basic* block, group count can either stay the same or decrease by one: + +> $$ +> sp \cdot \Delta gc \cdot (\Delta gc - 1) = 0 \text{ | degree} = 3 +> $$ + +When group count is decremented inside a *basic* block, either $h_0$ must be $0$ (we consumed all operations in a group) or we must be executing an operation with an immediate value: + +> $$ +> sp \cdot \Delta gc \cdot (1 - f_{imm})\cdot h_0 = 0 \text{ | degree} = 8 +> $$ + +Notice that the above constraint does not preclude $f_{imm} = 1$ and $h_0 = 0$ from being true at the same time. If this happens, op group decoding constraints (described [here](#op-group-decoding-constraints)) will force that the operation following the operation with an immediate value is a `NOOP`. + +When executing a `SPAN`, a `RESPAN`, or an operation with an immediate value, group count must be decremented by $1$: + +> $$ +> (f_{span} + f_{respan} + f_{imm}) \cdot (\Delta gc - 1) = 0 \text{ | degree} = 6 +> $$ + +If the next operation is either an `END` or a `RESPAN`, group count must remain the same: + +> $$ +> \Delta gc \cdot (f_{end}' + f_{respan}') = 0 \text{ | degree} = 5 +> $$ + +When an `END` operation is executed, group count must be $0$: + +> $$ +> f_{end} \cdot gc = 0 \text{ | degree} = 5 +> $$ + +### Op group decoding constraints +Inside a *basic* block, register $h_0$ is used to keep track of operations to be executed in the current operation group. The value of this register is set by the prover non-deterministically at the time when the prover executes a `SPAN` or a `RESPAN` operation, or when processing of a new operation group within a batch starts. The picture below illustrates this. + +![air_decoder_op_group_constraint](../../img/design/decoder/constraints/air_decoder_op_group_constraint.png) + +In the above: +* The prover sets the value of $h_0$ non-deterministically at row $0$. The value is set to an operation group containing operations `op0` through `op8`. +* As we start executing the group, at every row we "remove" the least significant operation from the group. This can be done by subtracting opcode of the operation from the group, and then dividing the result by $2^7$. +* By row $9$ the group is fully executed. This decrements the group count and set `op_index` to $0$ (constraints against `op_index` column are described in the next section). +* At row $10$ we start executing the next group with operations `op9` through `op11`. In this case, the prover populates $h_0$ with the group having its first operation (`op9`) already removed, and sets the `op_bits` registers to the value encoding `op9`. +* By row $12$ this group is also fully executed. + +To simplify the description of the constraints, we define the following variables: + +$$ +op = \sum_{i=0}^6 (b_i \cdot 2^i) +f_{sgc} = sp \cdot sp' \cdot (1 - \Delta gc) +$$ + +$op$ is just an opcode value implied by the values in `op_bits` registers. $f_{sgc}$ is a flag which is set to $1$ when the group count within a *basic* block does not change. We multiply it by $sp'$ to make sure the flag is $0$ when we are about to end decoding of an operation batch. Note that $f_{sgc}$ flag is mutually exclusive with $f_{span}$, $f_{respan}$, and $f_{imm}$ flags as these three operations decrement the group count. + +Using these variables, we can describe operation group decoding constraints as follows: + +When a `SPAN`, a `RESPAN`, or an operation with an immediate value is executed or when the group count does not change, the value in $h_0$ should be decremented by the value of the opcode in the next row. + +> $$ +> (f_{span} + f_{respan} + f_{imm} + f_{sgc}) \cdot (h_0 - h_0' \cdot 2^7 - op') = 0 \text{ | degree} = 6 +> $$ + +Notice that when the group count does change, and we are not executing $f_{span}$, $f_{respan}$, or $f_{imm}$ operations, no constraints are placed against $h_0$, and thus, the prover can populate this register non-deterministically. + +When we are in a *basic* block and the next operation is `END` or `RESPAN`, the current value in $h_0$ column must be $0$. + +> $$ +> sp \cdot (f_{end}' + f_{respan}') \cdot h_0 = 0 \text{ | degree} = 6 +> $$ + +### Op index constraints +The `op_index` column (denoted as $ox$) tracks index of an operation within its operation group. It is used to ensure that the number of operations executed per group never exceeds $9$. The index is zero-based, and thus, the possible set of values for $ox$ is between $0$ and $8$ (both inclusive). + +To simplify the description of the constraints, we will define the following variables: + +$$ +ng = \Delta gc - f_{imm} +\Delta ox = ox' - ox +$$ + +The value of $ng$ is set to $1$ when we are about to start executing a new operation group (i.e., group count is decremented but we did not execute an operation with an immediate value). Using these variables, we can describe the constraints against the $ox$ column as follows. + +When executing `SPAN` or `RESPAN` operations the next value of `op_index` must be set to $0$: + +> $$ +> (f_{span} + f_{respan}) \cdot ox' = 0 \text{ | degree} = 6 +> $$ + +When starting a new operation group inside a *basic* block, the next value of `op_index` must be set to $0$. Note that we multiply by $sp$ to exclude the cases when the group count is decremented because of `SPAN` or `RESPAN` operations: + +> $$ +> sp \cdot ng \cdot ox' = 0 \text{ | degree} = 6 +> $$ + +When inside a *basic* block but not starting a new operation group, `op_index` must be incremented by $1$. Note that we multiply by $sp'$ to exclude the cases when we are about to exit processing of an operation batch (i.e., the next operation is either `END` or `RESPAN`): + +> $$ +> sp \cdot sp' \cdot (1 - ng) \cdot (\Delta ox - 1) = 0 \text{ | degree} = 7 +> $$ + +Values of `op_index` must be in the range $[0, 8]$. + +> $$ +> \prod_{i=0}^{8}(ox - i) = 0 \text{ | degree} = 9 +> $$ + +### Op batch flags constraints +Operation batch flag columns (denoted $bc_0$, $bc_1$, and $bc_2$) are used to specify how many operation groups are present in an operation batch. This is relevant for the last batch in a basic block (or the first batch if there is only one batch in a block) as all other batches should be completely full (i.e., contain 8 operation groups). + +These columns are used to define the following 4 flags: + +* $f_{g8} = bc_0$: there are 8 operation groups in the batch. +* $f_{g4} = (1 - bc_0) \cdot bc_1 \cdot bc_2$: there are 4 operation groups in the batch. +* $f_{g2} = (1 - bc_0) \cdot (1 - bc_1) \cdot bc_2$: there are 2 operation groups in the batch. +* $f_{g1} = (1 - bc_0) \cdot bc_1 \cdot (1 - bc_2)$: there is only 1 operation groups in the batch. + +Notice that the degree of $f_{g8}$ is $1$, while the degree of the remaining flags is $3$. + +These flags can be set to $1$ only when we are executing `SPAN` or `RESPAN` operations as this is when the VM starts processing new operation batches. Also, for a given flag we need to ensure that only the specified number of operations groups are present in a batch. This can be done with the following constraints. + +All batch flags must be binary: + +> $$ +> bc_i^2 - bc_i = 0 \text{ for } i \in [0, 3) \text{ | degree} = 2 +> $$ + +When `SPAN` or `RESPAN` operations is executed, one of the batch flags must be set to $1$. + +> $$ +> (f_{span} + f_{respan}) - (f_{g1} + f_{g2} + f_{g4} + f_{g8}) = 0 \text{ | degree} = 5 +> $$ + +When neither `SPAN` nor `RESPAN` is executed, all batch flags must be set to $0$. + +$$ +(1 - (f_{span} + f_{respan})) \cdot (bc_0 + bc_1 + bc_2) = 0 \text{ | degree} = 6 +$$ + +When we have at most 4 groups in a batch, registers $h_4, ..., h_7$ should be set to $0$'s. + +> $$ +> (f_{g1} + f_{g2} + f_{g4}) \cdot h_i = 0 \text{ for } i \in [4, 8) \text{ | degree} = 4 +> $$ + +When we have at most 2 groups in a batch, registers $h_2$ and $h_3$ should also be set to $0$'s. + +> $$ +> (f_{g1} + f_{g2}) \cdot h_i = 0 \text{ for } i \in 2, 3 \text{ | degree} = 4 +> $$ + +When we have at most 1 groups in a batch, register $h_1$ should also be set to $0$. + +> $$ +> f_{g1} \cdot h_1 = 0 \text{ | degree} = 4 +> $$ + +### Op group table constraints +Op group table is used to ensure that all operation groups in a given batch are consumed before a new batch is started (i.e., via a `RESPAN` operation) or the execution of a *basic* block is complete (i.e., via an `END` operation). The op group table is updated according to the following rules: + +* When a new operation batch is started, we add groups from this batch to the table. To add a group to the table, we multiply the value in column $p_3$ by a value representing a tuple `(batch_id, group_pos, group)`. A constraint to enforce this would look as $p_3' = p_3 \cdot v$, where $v$ is the value representing the row to be added. Depending on the batch, we may need to add multiple groups to the table (i.e., $p_3' = p_3 \cdot v_1 \cdot v_2 \cdot v_3 ...$). Flags $f_{g1}$, $f_{g2}$, $f_{g4}$, and $f_{g8}$ are used to define how many groups to add. +* When a new operation group starts executing or when an immediate value is consumed, we remove the corresponding group from the table. To do this, we divide the value in column $p_3$ by a value representing a tuple `(batch_id, group_pos, group)`. A constraint to enforce this would look as $p_3' \cdot u = p_3$, where $u$ is the value representing the row to be removed. + +To simplify constraint descriptions, we first define variables representing the rows to be added to and removed from the op group table. + +When a `SPAN` or a `RESPAN` operation is executed, we compute the values of the rows to be added to the op group table as follows: + +$$ +v_i = \alpha_0 + \alpha_1 \cdot a' + \alpha_2 \cdot (gc - i) + \alpha_3 \cdot h_{i} \text{ | degree} = 1 +$$ + +Where $i \in [1, 8)$. Thus, $v_1$ defines row value for group in $h_1$, $v_2$ defines row value for group $h_2$ etc. Note that batch address column comes from the next row of the block address column ($a'$). + +We compute the value of the row to be removed from the op group table as follows: + +$$ +u = \alpha_0 + \alpha_1 \cdot a + \alpha_2 \cdot gc + \alpha_3 \cdot ((h_0' \cdot 2^7 + op') \cdot (1 - f_{imm}) + s_0' \cdot f_{push}) \text{ | degree} = 7 +$$ + +In the above, the value of the group is computed as $(h_0' \cdot 2^7 + op') \cdot (1 - f_{imm}) + s_0' \cdot f_{push}$. This basically says that when we execute a `PUSH` operation we need to remove the immediate value from the table. This value is at the top of the stack (column $s_0$) in the next row. However, when we are not executing a `PUSH` operation, the value to be removed is an op group value which is a combination of values in $h_0$ and `op_bits` columns (also in the next row). Note also that value for batch address comes from the current value in the block address column ($a$), and the group position comes from the current value of the group count column ($gc$). + +We also define a flag which is set to $1$ when a group needs to be removed from the op group table. + +$$ +f_{dg} = sp \cdot \Delta gc \text{ | degree} = 2 +$$ + +The above says that we remove groups from the op group table whenever group count is decremented. We multiply by $sp$ to exclude the cases when the group count is decremented due to `SPAN` or `RESPAN` operations. + +Using the above variables together with flags $f_{g2}$, $f_{g4}$, $f_{g8}$ defined in the previous section, we describe the constraint for updating op group table as follows (note that we do not use $f_{g1}$ flag as when a batch consists of a single group, nothing is added to the op group table): + +> $$ +> p_3' \cdot (f_{dg} \cdot u + 1 - f_{dg}) = p_3 \cdot (f_{g2} \cdot v_1 + f_{g4} \cdot \prod_{i=1}^3 v_i + f_{g8} \cdot (\prod_{i=1}^7 v_i) + 1 - (f_{span} + f_{respan})) +> $$ + +The above constraint specifies that: +* When `SPAN` or `RESPAN` operations are executed, we add between $1$ and $7$ groups to the op group table; else, leave $p3$ untouched. +* When group count is decremented inside a *basic* block, we remove a group from the op group table; else, leave $p3'$ untouched. + +The degree of this constraint is $9$. + +In addition to the above transition constraint, we also need to impose boundary constraints against the $p_3$ column to make sure the first and the last value in the column is set to $1$. This enforces that the op group table table starts and ends in an empty state. diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/design/decoder/index.md b/versioned_docs/version-0.12 (stable)/miden-vm/design/decoder/index.md new file mode 100644 index 0000000..d75cc56 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-vm/design/decoder/index.md @@ -0,0 +1,738 @@ +--- +title: "Miden VM Program Decoder" +sidebar_position: 1 +--- + +# Miden VM Program decoder + +Miden VM program decoder is responsible for ensuring that a program with a given [MAST root](../programs.md) is executed by the VM. As the VM executes a program, the decoder does the following: + +1. Decodes a sequence of field elements supplied by the prover into individual operation codes (or *opcodes* for short). +2. Organizes the sequence of field elements into code blocks, and computes the hash of the program according to the methodology described [here](../programs.md#program-hash-computation). + +At the end of program execution, the decoder outputs the computed program hash. This hash binds the sequence of opcodes executed by the VM to a program the prover claims to have executed. The verifier uses this hash during the STARK proof verification process to verify that the proof attests to a correct execution of a specific program (i.e., the prover didn't claim to execute program $A$ while in fact executing a different program $B$). + +The sections below describe how Miden VM decoder works. Throughout these sections we make the following assumptions: + +1. An opcode requires $7$ bits to represent. +2. An immediate value requires one full field element to represent. +3. A `NOOP` operation has a numeric value of $0$, and thus, can be encoded as seven zeros. Executing a `NOOP` operation does not change the state of the VM, but it does advance operation counter, and may affect program hash. + +## Program execution + +Miden VM programs consist of a set of code blocks organized into a binary tree. The leaves of the tree contain linear sequences of instructions, and control flow is defined by the internal nodes of the tree. + +Managing control flow in the VM is accomplished by executing control flow operations listed in the table below. Each of these operations requires exactly one VM cycle to execute. + +| Operation | Description | +| --------- | ---------------------------------------------------------------------------- | +| `JOIN` | Initiates processing of a new [Join block](../programs.md#join-block). | +| `SPLIT` | Initiates processing of a new [Split block](../programs.md#split-block). | +| `LOOP` | Initiates processing of a new [Loop block](../programs.md#loop-block). | +| `REPEAT` | Initiates a new iteration of an executing loop. | +| `SPAN` | Initiates processing of a new [Basic block](../programs.md#basic-block). (historically called "span block") | +| `RESPAN` | Initiates processing of a new operation batch within a basic block. (historically called "span block") | +| `DYN` | Initiates processing of a new [Dyn block](../programs.md#dyn-block). | +| `CALL` | Initiates processing of a new [Call block](../programs.md#call-block). | +| `SYSCALL` | Initiates processing ofa new [Syscall block](../programs.md#syscall-block). | +| `END` | Marks the end of a program block. | +| `HALT` | Marks the end of the entire program. | + +Let's consider a simple program below: + +``` +begin + + if.true + + else + + end +end +``` + +Block structure of this program is shown below. + +``` +JOIN + SPAN + + END + SPLIT + SPAN + + END + SPAN + + END + END +END +``` + +Executing this program on the VM can result in one of two possible instruction sequences. First, if after operations in `` are executed the top of the stack is $1$, the VM will execute the following: + +``` +JOIN +SPAN + +END +SPLIT +SPAN + +END +END +END +HALT +``` + +However, if after `` are executed, the top of the stack is $0$, the VM will execute the following: + +``` +JOIN +SPAN + +END +SPLIT +SPAN + +END +END +END +HALT +``` + +The main task of the decoder is to output exactly the same program hash, regardless of which one of the two possible execution paths was taken. However, before we can describe how this is achieved, we need to give an overview of the overall decoder structure. + +## Decoder structure + +The decoder is one of the more complex parts of the VM. It consists of the following components: + +* Main [execution trace](#decoder-trace) consisting of $24$ trace columns which contain the state of the decoder at a given cycle of a computation. +* Connection to the hash chiplet, which is used to offload [hash computations](#program-block-hashing) from the decoder. +* $3$ [virtual tables](#control-flow-tables) (implemented via multi-set checks), which keep track of code blocks and operations executing on the VM. + +### Decoder trace + +Decoder trace columns can be grouped into several logical sets of registers as illustrated below. + +![decoder_trace.png](../../img/design/decoder/decoder_trace.png) + +These registers have the following meanings: + +1. Block address register $a$. This register contains address of the hasher for the current block (row index from the auxiliary hashing table). It also serves the role of unique block identifiers. This is convenient, because hasher addresses are guaranteed to be unique. +2. Registers $b_0, ..., b_6$, which encode opcodes for operation to be executed by the VM. Each of these registers can contain a single binary value (either $1$ or $0$). And together these values describe a single opcode. +3. Hasher registers $h_0, ..., h_7$. When control flow operations are executed, these registers are used to provide inputs for the current block's hash computation (e.g., for `JOIN`, `SPLIT`, `LOOP`, `SPAN`, `CALL`, `SYSCALL` operations) or to record the result of the hash computation (i.e., for `END` operation). However, when regular operations are executed, $2$ of these registers are used to help with op group decoding, and the remaining $6$ can be used to hold operation-specific helper variables. +4. Register $sp$ which contains a binary flag indicating whether the VM is currently executing instructions inside a *basic* block (historically called "span block"). The flag is set to $1$ when the VM executes non-control flow instructions, and is set to $0$ otherwise. +5. Register $gc$ which keeps track of the number of unprocessed operation groups in a given *basic* block (historically called "span block"). +6. Register $ox$ which keeps track of a currently executing operation's index within its operation group. +7. Operation batch flags $c_0, c_1, c_2$ which indicate how many operation groups a given operation batch contains. These flags are set only for `SPAN` and `RESPAN` operations, and are set to $0$'s otherwise. +8. Two additional registers (not shown) are used primarily for constraint degree reduction. + +### Program block hashing + +To compute hashes of program blocks, the decoder relies on the [hash chiplet](../chiplets/hasher.md). Specifically, the decoder needs to perform two types of hashing operations: + +1. A simple 2-to-1 hash, where we provide a sequence of $8$ field elements, and get back $4$ field elements representing the result. Computing such a hash requires $8$ rows in the hash chiplet. +2. A sequential hash of $n$ elements. Computing such a hash requires multiple absorption steps, and at each step $8$ field elements are absorbed into the hasher. Thus, computing a sequential hash of $n$ elements requires $\lceil {n/8} \rceil$ rows in the hash chiplet. At the end, we also get $4$ field elements representing the result. + +To make hashing requests to the hash chiplet and to read the results from it, we will need to divide out relevant values from the [chiplets bus](../chiplets/index.md#chiplets-bus) column $b_{chip}$ as described below. + +#### Simple 2-to-1 hash + +To initiate a 2-to-1 hash of $8$ elements ($v_0, ..., v_7$) we need to divide $b_{chip}$ by the following value: + +$$ +\alpha_0 + \alpha_1 \cdot m_{bp} + \alpha_2 \cdot r + \sum_{i=0}^7 (\alpha_{i+8} \cdot v_i) +$$ + +where: +* $m_{bp}$ is a label indicating beginning of a new permutation. Value of this label is computed based on hash chiplet selector flags according to the methodology described [here](../chiplets/hasher.md#multiset-check-constraints). +* $r$ is the address of the row at which the hashing begins. +* Some $\alpha$ values are skipped in the above (e.g., $\alpha_3$) because of the specifics of how auxiliary hasher table rows are reduced to field elements (described [here](../chiplets/hasher.md#multiset-check-constraints)). For example, $\alpha_3$ is used as a coefficient for node index values during Merkle path computations in the hasher, and thus, is not relevant in this case. The $\alpha_4$ term is omitted when the number of items being hashed is a multiple of the rate width ($8$) because it is multiplied by 0 - the value of the first capacity register as determined by the [hasher chiplet logic](../chiplets/hasher.md#simple-2-to-1-hash). + +To read the $4$-element result ($u_0, ..., u_3$), we need to divide $b_{chip}$ by the following value: + +$$ +\alpha_0 + \alpha_1 \cdot m_{hout} + \alpha_2 \cdot (r + 7) + \sum_{i=0}^3 (\alpha_{i+8} \cdot u_i) +$$ + +where: +* $m_{hout}$ is a label indicating return of the hash value. Value of this label is computed based on hash chiplet selector flags according to the methodology described [here](../chiplets/hasher.md#multiset-check-constraints). +* $r$ is the address of the row at which the hashing began. + +#### Sequential hash + +To initiate a sequential hash of $n$ elements ($v_0, ..., v_{n-1}$), we need to divide $b_{chip}$ by the following value: + +$$ +\alpha_0 + \alpha_1 \cdot m_{bp} + \alpha_2 \cdot r + \alpha_4 \cdot n + \sum_{i=0}^7 (\alpha_{i+8} \cdot v_i) +$$ + +This also absorbs the first $8$ elements of the sequence into the hasher state. Then, to absorb the next sequence of $8$ elements (e.g., $v_8, ..., v_{15}$), we need to divide $b_{chip}$ by the following value: + +$$ +\alpha_0 + \alpha_1 \cdot m_{abp} + \alpha_2 \cdot (r + 7) + \sum_{i=0}^7 (\alpha_{i+8} \cdot v_{i + 8}) +$$ + +Where $m_{abp}$ is a label indicating absorption of more elements into the hasher state. Value of this label is computed based on hash chiplet selector flags according to the methodology described [here](../chiplets/hasher.md#multiset-check-constraints). + +We can keep absorbing elements into the hasher in the similar manner until all elements have been absorbed. Then, to read the result (e.g., $u_0, ..., u_3$), we need to divide $b_{chip}$ by the following value: + +$$ +\alpha_0 + \alpha_1 \cdot m_{hout} + \alpha_2 \cdot (r + \lceil n / 8 \rceil \cdot 8 - 1) + \sum_{i=0}^3 (\alpha_{i+8} \cdot u_i) +$$ + +Thus, for example, if $n = 14$, the result will of the hash will be available at hasher row $r + 15$. + +### Control flow tables + +In addition to the hash chiplet, control flow operations rely on $3$ virtual tables: *block stack* table, *block hash* table, and _op group_ table. These tables are virtual in that they don't require separate trace columns. Their state is described solely by running product columns: $p_1$, $p_2$, and $p_3$. The tables are described in the following sections. + +#### Block stack table + +When the VM starts executing a new program block, it adds its block ID together with the ID of its parent block (and some additional info) to the *block stack* table. When a program block is fully executed, it is removed from the table. In this way, the table represents a stack of blocks which are currently executing on the VM. By the time program execution completes, block stack table must be empty. + +The block stack table is also used to ensure that execution contexts are managed properly across the `CALL` and `SYSCALL` operations. + +The table can be thought of as consisting of $11$ columns as shown below: + +![decoder_block_stack_table](../../img/design/decoder/decoder_block_stack_table.png) + +where: +* The first column ($t_0$) contains the ID of the block. +* The second column ($t_1$) contains the ID of the parent block. If the block has no parent (i.e., it is a root block of the program), parent ID is 0. +* The third column ($t_2$) contains a binary value which is set to $1$ is the block is a *loop* block, and to $0$ otherwise. +* The following 8 columns are only set to non-zero values for `CALL` and `SYSCALL` operations. They save all the necessary information to be able to restore the parent context properly upon the corresponding `END` operation + - the `prnt_b0` and `prnt_b1` columns refer to the stack helper columns B0 and B1 (current stack depth and last overflow address, respectively) + +In the above diagram, the first 2 rows correspond to 2 different `CALL` operations. The first `CALL` operation is called from the root context, and hence its parent fn hash is the zero hash. Additionally, the second `CALL` operation has a parent fn hash of `[h0, h1, h2, h3]`, indicating that the first `CALL` was to a procedure with that hash. + +Running product column $p_1$ is used to keep track of the state of the table. At any step of the computation, the current value of $p_1$ defines which rows are present in the table. + +To reduce a row in the block stack table to a single value, we compute the following. + +$$ +row = \alpha_0 + \sum_{i=0}^{10} (\alpha_{i+1} \cdot t_i), +$$ + +where $\alpha_0, ..., \alpha_{11}$ are the random values provided by the verifier. + +#### Block hash table + +When the VM starts executing a new program block, it adds hashes of the block's children to the *block hash* table. And when the VM finishes executing a block, it removes its hash from the block hash table. Thus, by the time program execution completes, block hash table must be empty. + +The table can be thought of as consisting of $7$ columns as shown below: + +![block_hash_table](../../img/design/decoder/block_hash_table.png) + +where: +* The first column ($t_0$) contains the ID of the block's parent. For program root, parent ID is $0$. +* The next $4$ columns ($t_1, ..., t_4$) contain the hash of the block. +* The next column ($t_5$) contains a binary value which is set to $1$ if the block is the first child of a *join* block, and to $0$ otherwise. +* The last column ($t_6$) contains a binary value which is set to $1$ if the block is a body of a loop, and to $0$ otherwise. + +Running product column $p_2$ is used to keep track of the state of the table. At any step of the computation, the current value of $p_2$ defines which rows are present in the table. + +To reduce a row in the block hash table to a single value, we compute the following. + +$$ +row = \alpha_0 + \sum_{i=0}^6 (\alpha_{i+1} \cdot t_i) +$$ + +Where $\alpha_0, ..., \alpha_7$ are the random values provided by the verifier. + +Unlike other virtual tables, block hash table does not start out in an empty state. Specifically, it is initialized with a single row containing the hash of the program's root block. This needs to be done because the root block does not have a parent and, thus, otherwise it would never be added to the block hash table. + +Initialization of the block hash table is done by setting the initial value of $p_2$ to the value of the row containing the hash of a program's root block. + +#### Op group table +*Op group* table is used in decoding of *basic* blocks, which are leaves in a program's MAST. As described [here](../programs.md#basic-block), a *basic* block can contain one or more operation batches, each batch containing up to $8$ operation groups. + +When the VM starts executing a new batch of operations, it adds all operation groups within a batch, except for the first one, to the *op group* table. Then, as the VM starts executing an operation group, it removes the group from the table. Thus, by the time all operation groups in a batch have been executed, the *op group* table must be empty. + +The table can be thought of as consisting of $3$ columns as shown below: + +![decoder_op_group_table](../../img/design/decoder/decoder_op_group_table.png) + +The meaning of the columns is as follows: + +* The first column ($t_0$) contains operation batch ID. During the execution of the program, each operation batch is assigned a unique ID. +* The second column ($t_1$) contains the position of the group in the *basic* block (not just in the current batch). The position is $1$-based and is counted from the end. Thus, for example, if a *basic* block consists of a single batch with $4$ groups, the position of the first group would be $4$, the position of the second group would be $3$ etc. (the reason for this is explained in [this](#single-batch-span) section). Note that the group with position $4$ is not added to the table, because it is the first group in the batch, so the first row of the table will be for the group with position $3$. +* The third column ($t_2$) contains the actual values of operation groups (this could include up to $9$ opcodes or a single immediate value). + +Permutation column $p_3$ is used to keep track of the state of the table. At any step of the computation, the current value of $p_3$ defines which rows are present in the table. + +To reduce a row in the op group table to a single value, we compute the following. + +$$ +row = \alpha_0 + \sum_{i=0}^2 (\alpha_{i+1} \cdot t_i) +$$ + +Where $\alpha_0, ..., \alpha_3$ are the random values provided by the verifier. + +### Control flow operation semantics + +In this section we describe high-level semantics of executing all control flow operations. The descriptions are not meant to be complete and omit some low-level details. However, they provide good intuition on how these operations work. + +#### JOIN operation + +Before a `JOIN` operation is executed by the VM, the prover populates $h_0, ..., h_7$ registers with hashes of left and right children of the *join* program block as shown in the diagram below. + +![decoder_join_operation](../../img/design/decoder/decoder_join_operation.png) + +In the above diagram, `blk` is the ID of the *join* block which is about to be executed. `blk` is also the address of the hasher row in the auxiliary hasher table. `prnt` is the ID of the block's parent. + +When the VM executes a `JOIN` operation, it does the following: + +1. Adds a tuple `(blk, prnt, 0, 0...)` to the block stack table. +2. Adds tuples `(blk, left_child_hash, 1, 0)` and `(blk, right_child_hash, 0, 0)` to the block hash table. +3. Initiates a 2-to-1 hash computation in the hash chiplet (as described [here](#simple-2-to-1-hash)) using `blk` as row address in the auxiliary hashing table and $h_0, ..., h_7$ as input values. + +#### SPLIT operation + +Before a `SPLIT` operation is executed by the VM, the prover populates $h_0, ..., h_7$ registers with hashes of true and false branches of the *split* program block as shown in the diagram below. + +![decoder_split_operation](../../img/design/decoder/decoder_split_operation.png) + +In the above diagram, `blk` is the ID of the *split* block which is about to be executed. `blk` is also the address of the hasher row in the auxiliary hasher table. `prnt` is the ID of the block's parent. + +When the VM executes a `SPLIT` operation, it does the following: + +1. Adds a tuple `(blk, prnt, 0, 0...)` to the block stack table. +2. Pops the stack and:\ + a. If the popped value is $1$, adds a tuple `(blk, true_branch_hash, 0, 0)` to the block hash table.\ + b. If the popped value is $0$, adds a tuple `(blk, false_branch_hash, 0, 0)` to the block hash table.\ + c. If the popped value is neither $1$ nor $0$, the execution fails. +3. Initiates a 2-to-1 hash computation in the hash chiplet (as described [here](#simple-2-to-1-hash)) using `blk` as row address in the auxiliary hashing table and $h_0, ..., h_7$ as input values. + +#### LOOP operation + +Before a `LOOP` operation is executed by the VM, the prover populates $h_0, ..., h_3$ registers with hash of the loop's body as shown in the diagram below. + +![decoder_loop_operation](../../img/design/decoder/decoder_loop_operation.png) + +In the above diagram, `blk` is the ID of the *loop* block which is about to be executed. `blk` is also the address of the hasher row in the auxiliary hasher table. `prnt` is the ID of the block's parent. + +When the VM executes a `LOOP` operation, it does the following: + +1. Pops the stack and:\ + a. If the popped value is $1$ adds a tuple `(blk, prnt, 1, 0...)` to the block stack table (the `1` indicates that the loop's body is expected to be executed). Then, adds a tuple `(blk, loop_body_hash, 0, 1)` to the block hash table.\ + b. If the popped value is $0$, adds `(blk, prnt, 0, 0...)` to the block stack table. In this case, nothing is added to the block hash table.\ + c. If the popped value is neither $1$ nor $0$, the execution fails. +2. Initiates a 2-to-1 hash computation in the hash chiplet (as described [here](#simple-2-to-1-hash)) using `blk` as row address in the auxiliary hashing table and $h_0, ..., h_3$ as input values. + +#### SPAN operation + +Before a `SPAN` operation is executed by the VM, the prover populates $h_0, ..., h_7$ registers with contents of the first operation batch of the basic block as shown in the diagram below. The prover also sets the group count register $gc$ to the total number of operation groups in the basic block. + +![decoder_span_block](../../img/design/decoder/decoder_span_block.png) + +In the above diagram, `blk` is the ID of the *basic* block which is about to be executed. `blk` is also the address of the hasher row in the auxiliary hasher table. `prnt` is the ID of the block's parent. `g0_op0` is the first operation of the batch, and `g_0'` is the first operation group of the batch with the first operation removed. + +When the VM executes a `SPAN` operation, it does the following: + +1. Adds a tuple `(blk, prnt, 0, 0...)` to the block stack table. +2. Adds groups of the operation batch, as specified by op batch flags (see [here](#operation-batch-flags)) to the op group table. +3. Initiates a sequential hash computation in the hash chiplet (as described [here](#sequential-hash)) using `blk` as row address in the auxiliary hashing table and $h_0, ..., h_7$ as input values. +4. Sets the `in_span` register to $1$. +5. Decrements `group_count` register by $1$. +6. Sets the `op_index` register to $0$. + + +#### DYN operation + +![decoder_dyn_operation](../../img/design/decoder/decoder_dyn_operation.png) + +In the above diagram, `blk` is the ID of the *dyn* block which is about to be executed. `blk` is also the address of the hasher row in the auxiliary hasher table. `p_addr` is the ID of the block's parent. + +When the VM executes a `DYN` operation, it does the following: + +1. Adds a tuple `(blk, p_addr, 0, 0...)` to the block stack table. +2. Sends a memory read request to the memory chiplet, using `s0` as the memory address. The result `hash of callee` is placed in the decoder hasher trace at $h_0, h_1, h_2, h_3$. +3. Adds the tuple `(blk, hash of callee, 0, 0)` to the block hash table. +4. Initiates a 2-to-1 hash computation in the hash chiplet (as described [here](#simple-2-to-1-hash)) using `blk` as row address in the auxiliary hashing table and `[ZERO; 8]` as input values. +5. Performs a stack left shift + - Above `s16` was pulled from the stack overflow table if present; otherwise set to `0`. + +Note that unlike `DYNCALL`, the `ctx` and `fn_hash` registers are unchanged. + +#### DYNCALL operation + +![decoder_dyncall_operation](../../img/design/decoder/decoder_dyncall_operation.png) + +In the above diagram, `blk` is the ID of the *dyn* block which is about to be executed. `blk` is also the address of the hasher row in the auxiliary hasher table. `p_addr` is the ID of the block's parent. + +When the VM executes a `DYNCALL` operation, it does the following: + +1. Adds a tuple `(blk, p_addr, 0, ctx, b_0, b_1, fn_hash[0..3])` to the block stack table. +2. Sends a memory read request to the memory chiplet, using `s0` as the memory address. The result `hash of callee` is placed in the decoder hasher trace at $h_0, h_1, h_2, h_3$. +3. Sends a memory write request to the memory chiplet to set address `FMP_ADDR = 2^32 - 1` to `FMP_INIT_VALUE = 2^31` in the new memory context. This initializes the `fmp` in the new context. +4. Adds the tuple `(blk, hash of callee, 0, 0)` to the block hash table. +5. Initiates a 2-to-1 hash computation in the hash chiplet (as described [here](#simple-2-to-1-hash)) using `blk` as row address in the auxiliary hashing table and `[ZERO; 8]` as input values. +6. Performs a stack left shift + - Above `s16` was pulled from the stack overflow table if present; otherwise set to `0`. + +Similar to `CALL`, `DYNCALL` sets up a new `ctx`, and sets the `fn_hash` registers to the callee hash. + +#### END operation + +Before an `END` operation is executed by the VM, the prover populates $h_0, ..., h_3$ registers with the hash of the block which is about to end. The prover also sets values in $h_4$ and $h_5$ registers as follows: +* $h_4$ is set to $1$ if the block is a body of a *loop* block. We denote this value as `f0`. +* $h_5$ is set to $1$ if the block is a *loop* block. We denote this value as `f1`. +* $h_6$ is set to $1$ if the block is a *call* block. We denote this value as `f2`. +* $h_7$ is set to $1$ if the block is a *syscall* block. We denote this value as `f3`. + +![decoder_end_operation](../../img/design/decoder/decoder_end_operation.png) + +In the above diagram, `blk` is the ID of the block which is about to finish executing. `prnt` is the ID of the block's parent. + +When the VM executes an `END` operation, it does the following: + +1. Removes a tuple from the block stack table. + - if `f2` or `f3` is set, we remove a row `(blk, prnt, 0, ctx_next, b0_next, b1_next, fn_hash_next[0..4])` + - in the above, the `x_next` variables denote the column `x` in the next row + - else, we remove a row `(blk, prnt, f1, 0, 0, 0, 0, 0)` +2. Removes a tuple `(prnt, current_block_hash, nxt, f0)` from the block hash table, where $nxt=0$ if the next operation is either `END` or `REPEAT`, and $1$ otherwise. +3. Reads the hash result from the hash chiplet (as described [here](#program-block-hashing)) using `blk + 7` as row address in the auxiliary hashing table. +4. If $h_5 = 1$ (i.e., we are exiting a *loop* block), pops the value off the top of the stack and verifies that the value is $0$. +5. Verifies that `group_count` register is set to $0$. + +#### HALT operation + +Before a `HALT` operation is executed by the VM, the VM copies values in $h_0, ..., h_3$ registers to the next row as illustrated in the diagram below: + +![decoder_halt_operation](../../img/design/decoder/decoder_halt_operation.png) + +In the above diagram, `blk` is the ID of the block which is about to finish executing. + +When the VM executes a `HALT` operation, it does the following: + +1. Verifies that block address register is set to $0$. +2. If we are not at the last row of the trace, verifies that the next operation is `HALT`. +3. Copies values of $h_0, ..., h_3$ registers to the next row. +4. Populates all other decoder registers with $0$'s in the next row. + +#### REPEAT operation + +Before a `REPEAT` operation is executed by the VM, the VM copies values in registers $h_0, ..., h_4$ to the next row as shown in the diagram below. + +![decoder_repeat_operation](../../img/design/decoder/decoder_repeat_operation.png) + +In the above diagram, `blk` is the ID of the loop's body and `prnt` is the ID of the loop. + +When the VM executes a `REPEAT` operation, it does the following: + +1. Checks whether register $h_4$ is set to $1$. If it isn't (i.e., we are not in a loop), the execution fails. +2. Pops the stack and if the popped value is $1$, adds a tuple `(prnt, loop_body_loop 0, 1)` to the block hash table. If the popped value is not $1$, the execution fails. + +The effect of the above is that the VM needs to execute the loop's body again to clear the block hash table. + +#### RESPAN operation + +Before a `RESPAN` operation is executed by the VM, the VM copies the ID of the current block `blk` and the number of remaining operation groups in the basic block to the next row, and sets the value of `in_span` column to $0$. The prover also sets the value of $h_1$ register for the next row to the ID of the current block's parent `prnt` as shown in the diagram below: + +![decoder_respan_operation](../../img/design/decoder/decoder_respan_operation.png) + +In the above diagram, `g0_op0` is the first operation of the new operation batch, and `g0'` is the first operation group of the batch with `g0_op0` operation removed. + +When the VM executes a `RESPAN` operation, it does the following: + +1. Increments block address by $8$. +2. Removes the tuple `(blk, prnt, 0, 0...)` from the block stack table. +3. Adds the tuple `(blk+8, prnt, 0, 0...)` to the block stack table. +4. Absorbs values in registers $h_0, ..., h_7$ into the hasher state of the hash chiplet (as described [here](#sequential-hash)). +5. Sets the `in_span` register to $1$. +6. Adds groups of the operation batch, as specified by op batch flags (see [here](#operation-batch-flags)) to the op group table using `blk+8` as batch ID. + +The net result of the above is that we incremented the ID of the current block by $8$ and added the next set of operation groups to the op group table. + +#### CALL operation + +Recall that the purpose of a `CALL` operation is to execute a procedure in a new execution context. Specifically, this means that the entire memory is zero'd in the new execution context, and the stack is truncated to a depth of 16 (i.e. any element in the stack overflow table is not available in the new context). On the corresponding `END` instruction, the prover will restore the previous execution context (verified by the block stack table). + +Before a `CALL` operation, the prover populates $h_0, ..., h_3$ registers with the hash of the procedure being called. In the next row, the prover + +- sets the context ID to the next row's CLK value +- sets the `fn hash` registers to the hash of the callee + - This register is what the `caller` instruction uses to return the hash of the caller +- resets the stack `B0` register to 16 (which tracks the current stack depth) +- resets the overflow address to 0 (which tracks the "address" of the last element added to the overflow table) + - it is set to 0 to indicate that the overflow table is empty + +![decoder_call_operation](../../img/design/decoder/decoder_call_operation.png) + +In the above diagram, `blk` is the ID of the *call* block which is about to be executed. `blk` is also the address of the hasher row in the auxiliary hasher table. `prnt` is the ID of the block's parent. + +When the VM executes a `CALL` operation, it does the following: + +1. Adds a tuple `(blk, prnt, 0, p_ctx, p_b0, p_b1, prnt_fn_hash[0..4])` to the block stack table. +2. Initiates a 2-to-1 hash computation in the hash chiplet (as described [here](#simple-2-to-1-hash)) using `blk` as row address in the auxiliary hashing table and $h_0, ..., h_3$ as input values. +3. Sends a memory write request to the memory chiplet to set address `FMP_ADDR = 2^32 - 1` to `FMP_INIT_VALUE = 2^31` in the new memory context. This initializes the `fmp` in the new context. + +#### SYSCALL operation + +Similarly to the `CALL` operation, a `SYSCALL` changes the execution context. However, it always jumps back to the root context, and executes kernel procedures only. + +Before a `SYSCALL` operation, the prover populates $h_0, ..., h_3$ registers with the hash of the procedure being called. In the next row, the prover + +- sets the context ID to 0, +- does NOT modify the `fn hash` register + - Hence, the `fn hash` register contains the procedure hash of the caller, to be accessed by the `caller` instruction, +- resets the stack `B0` register to 16 (which tracks the current stack depth) +- resets the overflow address to 0 (which tracks the "address" of the last element added to the overflow table) + - it is set to 0 to indicate that the overflow table is empty + +![decoder_syscall_operation](../../img/design/decoder/decoder_syscall_operation.png) + +In the above diagram, `blk` is the ID of the *syscall* block which is about to be executed. `blk` is also the address of the hasher row in the auxiliary hasher table. `prnt` is the ID of the block's parent. + +When the VM executes a `SYSCALL` operation, it does the following: + +1. Adds a tuple `(blk, prnt, 0, p_ctx, p_b0, p_b1, prnt_fn_hash[0..4])` to the block stack table. +2. Sends a request to the kernel ROM chiplet indicating that `hash of callee` is being accessed. + - this results in a fault if `hash of callee` does not correspond to the hash of a kernel procedure +3. Initiates a 2-to-1 hash computation in the hash chiplet (as described [here](#simple-2-to-1-hash)) using `blk` as row address in the auxiliary hashing table and $h_0, ..., h_3$ as input values. + +## Program decoding + +When decoding a program, we start at the root block of the program. We can compute the hash of the root block directly from hashes of its children. The prover provides hashes of the child blocks non-deterministically, and we use them to compute the program's hash (here we rely on the hash chiplet). We then verify the program hash via boundary constraints. Thus, if the prover provided valid hashes for the child blocks, we will get the expected program hash. + +Now, we need to verify that the VM executed the child blocks correctly. We do this recursively similar to what is described above: for each of the blocks, the prover provides hashes of its children non-deterministically and we verify that the hash has been computed correctly. We do this until we get to the leaf nodes (i.e., *basic* blocks). Hashes of *basic* blocks are computed sequentially from the instructions executed by the VM. + +The sections below illustrate how different types of code blocks are decoded by the VM. + +### JOIN block decoding + +When decoding a *join* bock, the VM first executes a `JOIN` operation, then executes the first child block, followed by the second child block. Once the children of the *join* block are executed, the VM executes an `END` operation. This is illustrated in the diagram below. + +![decoder_join_block_decoding](../../img/design/decoder/decoder_join_block_decoding.png) + +As described previously, when the VM executes a `JOIN` operation, hashes of both children are added to the block hash table. These hashes are removed only when the `END` operations for the child blocks are executed. Thus, until both child blocks are executed, the block hash table is not cleared. + +### SPLIT block decoding + +When decoding a *split* block, the decoder pops an element off the top of the stack, and if the popped element is $1$, executes the block corresponding to the `true branch`. If the popped element is $0$, the decoder executes the block corresponding to the `false branch`. This is illustrated on the diagram below. + +![decoder_split_block_decoding](../../img/design/decoder/decoder_split_block_decoding.png) + +As described previously, when the VM executes a `SPLIT` operation, only the hash of the branch to be executed is added to the block hash table. Thus, until the child block corresponding to the required branch is executed, the block hash table is not cleared. + +### LOOP block decoding + +When decoding a *loop* bock, we need to consider two possible scenarios: + +* When the top of the stack is $1$, we need to enter the loop and execute loop body at least once. +* When the top of the stack is, $0$ we need to skip the loop. + +In both cases, we need to pop an element off the top of the stack. + +#### Executing the loop + +If the top of the stack is $1$, the VM executes a `LOOP` operation. This removes the top element from the stack and adds the hash of the loop's body to the block hash table. It also adds a row to the block stack table setting the `is_loop` value to $1$. + +To clear the block hash table, the VM needs to execute the loop body (executing the `END` operation for the loop body block will remove the corresponding row from the block hash table). After loop body is executed, if the top of the stack is $1$, the VM executes a `REPEAT` operation (executing `REPEAT` operation when the top of the stack is $0$ will result in an error). This operation again adds the hash of the loop's body to the block hash table. Thus, the VM needs to execute the loop body again to clear the block hash table. + +This process is illustrated on the diagram below. + +![decoder_loop_execution](../../img/design/decoder/decoder_loop_execution.png) + +The above steps are repeated until the top of the stack becomes $0$, at which point the VM executes the `END` operation. Since in the beginning we set `is_loop` column in the block stack table to $1$, $h_6$ column will be set to $1$ when the `END` operation is executed. Thus, executing the `END` operation will also remove the top value from the stack. If the removed value is not $0$, the operation will fail. Thus, the VM can exit the loop block only when the top of the stack is $0$. + +#### Skipping the loop + +If the top of the stack is $0$, the VM still executes the `LOOP` operation. But unlike in the case when we need to enter the loop, the VM sets `is_loop` flag to $0$ in the block stack table, and does not add any rows to the block hash table. The last point means that the only possible operation to be executed after the `LOOP` operation is the `END` operation. This is illustrated in the diagram below. + +![decoder_loop_skipping](../../img/design/decoder/decoder_loop_skipping.png) + +Moreover, since we've set the `is_loop` flag to $0$, executing the `END` operation does not remove any items from the stack. + +### DYN block decoding + +When decoding a *dyn* bock, the VM first executes a `DYN` operation, then executes the child block dynamically specified by the top of the stack. Once the child of the *dyn* block has been executed, the VM executes an `END` operation. This is illustrated in the diagram below. + +![decoder_dyn_block_decoding](../../img/design/decoder/decoder_dyn_block_decoding.png) + +As described previously, when the VM executes a `DYN` operation, the hash of the child is added to the block hash table. This hash is removed only when the `END` operation for the child block is executed. Thus, until the child block corresponding to the dynamically specified target is executed, the block hash table is not cleared. + +### Basic block decoding + +As described [here](../programs.md#basic-block), a *basic* block can contain one or more operation batches, each batch containing up to $8$ operation groups. At the high level, decoding of a basic block is done as follows: + +1. At the beginning of the block, we make a request to the hash chiplet which initiates the hasher, absorbs the first operation batch ($8$ field elements) into the hasher, and returns the row address of the hasher, which we use as the unique ID for the *basic* block (see [here](#sequential-hash)). +2. We then add groups of the operation batch, as specified by op batch flags (but always skipping the first one) to the op group table. +3. We then remove operation groups from the op group table in the FIFO order one by one, and decode them in the manner similar to the one described [here](#operation-group-decoding). +4. Once all operation groups in a batch have been decoded, we absorb the next batch into the hasher and repeat the process described above. +5. Once all batches have been decoded, we return the hash of the basic block from the hasher. + +Overall, three control flow operations are used when decoding a *basic* block: + +1. `SPAN` operation is used to initialize a hasher and absorbs the first operation batch into it. +2. `RESPAN` operation is used to absorb any additional batches in the basic block. +3. `END` operation is used to end the decoding of a basic block and retrieve its hash from the hash chiplet. + +#### Operation group decoding + +As described [here](../programs.md#basic-block), an operation group is a sequence of operations which can be encoded into a single field element. For a field element of $64$ bits, we can fit up to $9$ operations into a group. We do this by concatenating binary representations of opcodes together with the first operation located in the least significant position. + +We can read opcodes from the group by simply subtracting them from the op group value and then dividing the result by $2^7$. Once the value of the op group reaches $0$, we know that all opcodes have been read. Graphically, this can be illustrated like so: + +![decoder_operation_group_decoding](../../img/design/decoder/decoder_operation_group_decoding.png) + +Notice that despite their appearance, `op bits` is actually $7$ separate registers, while `op group` is just a single register. + +We also need to make sure that at most $9$ operations are executed as a part of a single group. For this purpose we use the `op_index` column. Values in this column start out at $0$ for each operation group, and are incremented by $1$ for each executed operation. To make sure that at most $9$ operations can be executed in a group, the value of the `op_index` column is not allowed to exceed $8$. + +#### Operation batch flags + +Operation batch flags are used to specify how many operation groups comprise a given operation batch. For most batches, the number of groups will be equal to $8$. However, for the last batch in a block (or for the first batch, if the block consists of only a single batch), the number of groups may be less than $8$. Since processing of new batches starts only on `SPAN` and `RESPAN` operations, only for these operations the flags can be set to non-zero values. + +To simplify the constraint system, the number of groups in a batch can be only one of the following values: $1$, $2$, $4$, and $8$. If the number of groups in a batch does not match one of these values, the batch is simply padded with `NOOP`'s (one `NOOP` per added group). Consider the diagram below. + +![decoder_OPERATION_batch_flags](../../img/design/decoder/decoder_OPERATION_batch_flags.png) + +In the above, the batch contains $3$ operation groups. To bring the count up to $4$, we consider the $4$-th group (i.e., $0$) to be a part of the batch. Since a numeric value for `NOOP` operation is $0$, op group value of $0$ can be interpreted as a single `NOOP`. + +Operation batch flags (denoted as $c_0, c_1, c_2$), encode the number of groups and define how many groups are added to the op group table as follows: + +* `(1, -, -)` - $8$ groups. Groups in $h_1, ... h_7$ are added to the op group table. +* `(0, 1, 0)` - $4$ groups. Groups in $h_1, ... h_3$ are added to the op group table +* `(0, 0, 1)` - $2$ groups. Groups in $h_1$ is added to the op group table. +* `(0, 1, 1)` - $1$ group. Nothing is added to the op group table +* `(0, 0, 0)` - not a `SPAN` or `RESPAN` operation. + +#### Single-batch span + +The simplest example of a *basic* block is a block with a single batch. This batch may contain up to $8$ operation groups (e.g., $g_0, ..., g_7$). Decoding of such a block is illustrated in the diagram below. + +![decoder_single_batch_span](../../img/design/decoder/decoder_single_batch_span.png) + +Before the VM starts processing this *basic* block, the prover populates registers $h_0, ..., h_7$ with operation groups $g_0, ..., g_7$. The prover also puts the total number of groups into the `group_count` register $gc$. In this case, the total number of groups is $8$. + +When the VM executes a `SPAN` operation, it does the following: + +1. Initiates hashing of elements $g_0, ..., g_7$ using hash chiplet. The hasher address is used as the block ID `blk`, and it is inserted into `addr` register in the next row. +2. Adds a tuple `(blk, prnt, 0)` to the block stack table. +3. Sets the `is_span` register to $1$ in the next row. +4. Sets the `op_index` register to $0$ in the next row. +5. Decrements `group_count` register by $1$. +6. Sets `op bits` registers at the next step to the first operation of $g_0$, and also copies $g_0$ with the first operation removed (denoted as $g_0'$) to the next row. +7. Adds groups $g_1, ..., g_7$ to the op group table. Thus, after the `SPAN` operation is executed, op group table looks as shown below. + +![decoder_op_group_table_after_span_op](../../img/design/decoder/decoder_op_group_table_after_span_op.png) + +Then, with every step the next operation is removed from $g_0$, and by step $9$, the value of $g_0$ is $0$. Once this happens, the VM does the following: + +1. Decrements `group_count` register by $1$. +2. Sets `op bits` registers at the next step to the first operation of $g_1$. +3. Sets `hasher` register $h_0$ to the value of $g_1$ with the first operation removed (denoted as $g_1'$). +4. Removes row `(blk, 7, g1)` from the op group table. This row can be obtained by taking values from registers: `addr`, `group_count`, and $h_0' + \displaystyle\sum_{i=0}^6(2^i \cdot b_i')$ for $i \in [0, 7)$, where $h_0'$ and $b_i'$ refer to values in the next row for the first hasher column and `op_bits` columns respectively. + +Note that we rely on the `group_count` column to construct the row to be removed from the op group table. Since group count is decremented from the total number of groups to $0$, to remove groups from the op group table in correct order, we need to assign group position to groups in the op group table in the reverse order. For example, the first group to be removed should have position $7$, the second group to be removed should have position $6$ etc. + +Decoding of $g_1$ is performed in the same manner as decoding of $g_0$: with every subsequent step the next operation is removed from $g_1$ until its value reaches $0$, at which point, decoding of group $g_2$ begins. + +The above steps are executed until value of `group_count` reaches $0$. Once `group_count` reaches $0$ and the last operation group $g_7$ is executed, the VM executes the `END` operation. Semantics of the `END` operation are described [here](#end-operation). + +Notice that by the time we get to the `END` operation, all rows are removed from the op group table. + +#### Multi-batch basic block + +A *basic* block may contain an unlimited number of operation batches. As mentioned previously, to absorb a new batch into the hasher, the VM executes a `RESPAN` operation. The diagram below illustrates decoding of a *basic* block consisting of two operation batches. + +![decoder_multi_batch_span](../../img/design/decoder/decoder_multi_batch_span.png) + +Decoding of such a block will look very similar to decoding of the single-basic block described previously, but there also will be some differences. + +First, after the `SPAN` operation is executed, the op group table will look as follows: + +![decoder_op_group_table_multi_span](../../img/design/decoder/decoder_op_group_table_multi_span.png) + +Notice that while the same groups ($g_1, ..., g_7$) are added to the table, their positions now reflect the total number of groups in the *basic* block. + +Second, executing a `RESPAN` operation increments hasher address by $8$. This is done because absorbing additional $8$ elements into the hasher state requires $8$ more rows in the auxiliary hasher table. + +Incrementing value of `addr` register actually changes the ID of the *basic* block (though, for a *basic* block, it may be more appropriate to view values in this column as IDs of individual operation batches). This means that we also need to update the block stack table. Specifically, we need to remove row `(blk, prnt, 0)` from it, and replace it with row `(blk + 8, prnt, 0)`. To perform this operation, the prover sets the value of $h_1` in the next row to `prnt`. + +Executing a `RESPAN` operation also adds groups $g_9, g_{10}, g_{11}$ to the op group table, which now would look as follows: + +![decoder_op_group_table_post_respan](../../img/design/decoder/decoder_op_group_table_post_respan.png) + +Then, the execution of the second batch proceeds in a manner similar to the first batch: we remove operations from the current op group, execute them, and when the value of the op group reaches $0$, we start executing the next group in the batch. Thus, by the time we get to the `END` operation, the op group table should be empty. + +When executing the `END` operation, the hash of the *basic* block will be read from hasher row at address `addr + 7`, which, in our example, will be equal to `blk + 15`. + +#### Handling immediate values + +Miden VM operations can carry immediate values. Currently, the only such operation is a `PUSH` operation. Since immediate values can be thought of as constants embedded into program code, we need to make sure that changing immediate values affects program hash. + +To achieve this, we treat immediate values in a manner similar to how we treat operation groups. Specifically, when computing hash of a *basic* block, immediate values are absorbed into the hasher state in the same way as operation groups are. As mentioned previously, an immediate value is represented by a single field element, and thus, an immediate value takes place of a single operation group. + +The diagram below illustrates decoding of a *basic* block with $9$ operations one of which is a `PUSH` operation. + +![decoder_decoding_span_block_with_push](../../img/design/decoder/decoder_decoding_span_block_with_push.png) + +In the above, when the `SPAN` operation is executed, immediate value `imm0` will be added to the op group table, which will look as follows: + +![decoder_imm_vale_op_group_table](../../img/design/decoder/decoder_imm_vale_op_group_table.png) + +Then, when the `PUSH` operation is executed, the VM will do the following: + +1. Decrement `group_count` by $1$. +2. Remove a row from the op group table equal to `(addr, group_count, s0')`, where $s_0'$ is the value of the top of the stack at the next row (i.e., it is the value that is pushed onto the stack). + +Thus, after the `PUSH` operation is executed, the op group table is cleared, and group count decreases to $0$ (which means that there are no more op groups to execute). Decoding of the rest of the op group proceeds as described in the previous sections. + +## Program decoding example + +Let's run through an example of decoding a simple program shown previously: + +``` +begin + + if.true + + else + + end +end +``` + +Translating this into code blocks with IDs assigned, we get the following: + +``` +b0: JOIN + b1: SPAN + + b1: END + b2: SPLIT + b3: SPAN + + b3: END + b4: SPAN + + b4: END + b2: END +b0: END +``` + +The root of the program is a *join* block $b_0$. This block contains two children: a *basic* bock $b_1$ and a *split* block $b_2$. In turn, the *split* block $b_2$ contains two children: a *basic* block $b_3$ and a *basic* block $b_4$. + +When this program is executed on the VM, the following happens: + +1. Before the program starts executing, block hash table is initialized with a single row containing the hash of $b_0$. +2. Then, `JOIN` operation for $b_0$ is executed. It adds hashes of $b_1$ and $b_2$ to the block hash table. It also adds an entry for $b_0$ to the block stack table. States of both tables after this step are illustrated below. +3. Then, *basic* $b_1$ is executed and a sequential hash of its operations is computed. Also, when `SPAN` operation for $b_1$ is executed, an entry for $b_1$ is added to the block stack table. At the end of $b_1$ (when `END` is executed), entries for $b_1$ are removed from both the block hash and block stack tables. +4. Then, `SPLIT` operation for $b_2$ is executed. It adds an entry for $b_2$ to the block stack table. Also, depending on whether the top of the stack is $1$ or $0$, either hash of $b_3$ or hash of $b_4$ is added to the block hash table. Let's say the top of the stack is $1$. Then, at this point, the block hash and block stack tables will look like in the second picture below. +5. Then, *basic* $b_3$ is executed and a sequential hash of its instructions is computed. Also, when `SPAN` operation for $b_3$ is executed, an entry for $b_3$ is added to the block stack table. At the end of $b_3$ (when `END` is executed), entries for $b_3$ are removed from both the block hash and block stack tables. +6. Then, `END` operation for $b_2$ is executed. It removes the hash of $b_2$ from the block hash table, and also removes the entry for $b_2$ from the block stack table. The third picture below illustrates the states of block stack and block hash tables after this step. +7. Then, `END` for $b_0$ is executed, which removes entries for $b_0$ from the block stack and block hash tables. At this point both tables are empty. +8. Finally, a sequence of `HALT` operations is executed until the length of the trace reaches a power of two. + +States of block hash and block stack tables after step 2: +![decoder_state_block_hash_2](../../img/design/decoder/decoder_state_block_hash_2.png) + +States of block hash and block stack tables after step 4: +![decoder_state_block_hash_4](../../img/design/decoder/decoder_state_block_hash_4.png) + +States of block hash and block stack tables after step 6: +![decoder_state_block_hash_6](../../img/design/decoder/decoder_state_block_hash_6.png) diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/design/index.md b/versioned_docs/version-0.12 (stable)/miden-vm/design/index.md new file mode 100644 index 0000000..f9853c2 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-vm/design/index.md @@ -0,0 +1,56 @@ +--- +title: "Design" +sidebar_position: 1 +--- + +# Design +In the following sections, we provide in-depth descriptions of Miden VM internals, including all AIR constraints for the proving system. We also provide rationale for making specific design choices. + +Throughout these sections we adopt the following notations and assumptions: +* All arithmetic operations, unless noted otherwise, are assumed to be in a prime field with modulus $p = 2^{64} - 2^{32} + 1$. +* A _binary_ value means a field element which is either $0$ or $1$. +* We use lowercase letters to refer to individual field elements (e.g., $a$), and uppercase letters to refer to groups of $4$ elements, also referred to as words (e.g., $A$). To refer to individual elements within a word, we use numerical subscripts. For example, $a_0$ is the first element of word $A$, $b_3$ is the last element of word $B$, etc. +* When describing AIR constraints: + - For a column $x$, we denote the value in the current row simply as $x$, and the value in the next row of the column as $x'$. Thus, all transition constraints for Miden VM work with two consecutive rows of the execution trace. + - For multiset equality constraints, we denote random values sent by the verifier after the prover commits to the main execution trace as $\alpha_0, \alpha_1, \alpha_2$ etc. + - To differentiate constraints from other formulas, we frequently use the following format for constraint equations. + +$$ +x' - (x + y) = 0 \text{ | degree} = 1 +$$ + +In the above, the constraint equation is followed by the implied algebraic degree of the constraint. This degree is determined by the number of multiplications between trace columns. If a constraint does not involve any multiplications between columns, its degree is $1$. If a constraint involves multiplication between two columns, its degree is $2$. If we need to multiply three columns together, the degree is $3$ ect. + +The maximum allowed constraint degree in Miden VM is $9$. If a constraint degree grows beyond that, we frequently need to introduce additional columns to reduce the degree. + +## VM components +Miden VM consists of several interconnected components, each providing a specific set of functionality. These components are: + +* **System**, which is responsible for managing system data, including the current VM cycle (`clk`), and the current and parent execution contexts. +* **Program decoder**, which is responsible for computing a commitment to the executing program and converting the program into a sequence of operations executed by the VM. +* **Operand stack**, which is a push-down stack which provides operands for all operations executed by the VM. +* **Range checker**, which is responsible for providing 16-bit range checks needed by other components. +* **Chiplets**, which is a set of specialized circuits used to accelerate commonly-used complex computations. Currently, the VM relies on 4 chiplets: + - Hash chiplet, used to compute Rescue Prime Optimized hashes both for sequential hashing and for Merkle tree hashing. + - Bitwise chiplet, used to compute bitwise operations (e.g., `AND`, `XOR`) over 32-bit integers. + - Memory chiplet, used to support random-access memory in the VM. + - Kernel ROM chiplet, used to enable calling predefined kernel procedures which are provided before execution begins. + +The above components are connected via **buses**, which are implemented using [lookup arguments](./lookups/index.md). We also use [multiset check lookups](./lookups/multiset.md) internally within components to describe **virtual tables**. + +## VM execution trace +The execution trace of Miden VM consists of $71$ main trace columns, $2$ buses, and $5$ virtual tables, as shown in the diagram below. + +![vm_trace.png](../img/design/vm_trace.png) + +As can be seen from the above, the system, decoder, stack, and range checker components use dedicated sets of columns, while all chiplets share the same $18$ columns. To differentiate between chiplets, we use a set of binary selector columns, a combination of which uniquely identifies each chiplet. + +The system component does not yet have a dedicated documentation section, since the design is likely to change. However, the following column is not expected to change: + +* `clk` which is used to keep track of the current VM cycle. Values in this column start out at $0$ and are incremented by $1$ with each cycle. + +For the `clk` column, the constraints are straightforward: + +$$ +clk' - (clk + 1) = 0 \text{ | degree} = 1 +$$ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/design/lookups/_category_.yml b/versioned_docs/version-0.12 (stable)/miden-vm/design/lookups/_category_.yml new file mode 100644 index 0000000..d76cc7b --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-vm/design/lookups/_category_.yml @@ -0,0 +1,4 @@ +label: "Lookup arguments" +# Determines where this documentation section appears relative to other sections in the parent folder +position: 6 +collapsed: true diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/design/lookups/index.md b/versioned_docs/version-0.12 (stable)/miden-vm/design/lookups/index.md new file mode 100644 index 0000000..151c834 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-vm/design/lookups/index.md @@ -0,0 +1,60 @@ +--- +title: "Lookup Arguments in Miden VM" +sidebar_position: 1 +--- + +# Lookup arguments in Miden VM + +Zero knowledge virtual machines frequently make use of lookup arguments to enable performance optimizations. Miden VM uses two types of arguments: multiset checks and a multivariate lookup based on logarithmic derivatives known as LogUp. A brief introduction to multiset checks can be found [here](./multiset.md). The description of LogUp can be found [here](https://eprint.iacr.org/2022/1530.pdf). + +In Miden VM, lookup arguments are used for two purposes: + +1. To prove the consistency of intermediate values that must persist between different cycles of the trace without storing the full data in the execution trace (which would require adding more columns to the trace). +2. To prove correct interaction between two independent sections of the execution trace, e.g., between the main trace where the result of some operation is required, but would be expensive to compute, and a specialized component which can perform that operation cheaply. + +The first is achieved using [virtual tables](#virtual-tables-in-miden-vm) of data, where we add a row at some cycle in the trace and remove it at a later cycle when it is needed again. Instead of maintaining the entire table in the execution trace, multiset checks allow us to prove data consistency of this table using one running product column. + +The second is done by reducing each operation to a lookup value and then using a [communication bus](#communication-buses-in-miden-vm) to provably connect the two sections of the trace. These communication buses can be implemented either via [multiset checks](./multiset.md#communication-buses) or via the [LogUp argument](./logup.md). + + +## Virtual tables in Miden VM + +Miden VM makes use of 6 virtual tables across 4 components, all of which are implemented via [multiset checks](./multiset.md#virtual-tables): + +- Stack: + - [Overflow table](../stack/index.md#overflow-table) +- Decoder: + - [Block stack table](../decoder/index.md#block-stack-table) + - [Block hash table](../decoder/index.md#block-hash-table) + - [Op group table](../decoder/index.md#op-group-table) +- Chiplets: + - [Chiplets virtual table](../chiplets/index.md#chiplets-virtual-table), which combines the following two tables into one: + - [Hash chiplet sibling table](../chiplets/hasher.md#sibling-table-constraints) + - [Kernel ROM chiplet procedure table](../chiplets/kernel_rom.md#constraints) + +## Communication buses in Miden VM + +One strategy for improving the efficiency of a zero knowledge virtual machine is to use specialized components for complex operations and have the main circuit “offload” those operations to the corresponding components by specifying inputs and outputs and allowing the proof of execution to be done by the dedicated component instead of by the main circuit. + +These specialized components are designed to prove the internal correctness of the execution of the operations they support. However, in isolation they cannot make any guarantees about the source of the input data or the destination of the output data. + +In order to prove that the inputs and outputs specified by the main circuit match the inputs and outputs provably executed in the specialized component, some kind of provable communication bus is needed. + +This bus is typically implemented as some kind of lookup argument, and in Miden VM in particular we use multiset checks or LogUp. + +Miden VM uses 2 communication buses: + +- The chiplets bus [$b_{chip}$](../chiplets/index.md#chiplets-bus), which communicates with all of the chiplets (Hash, Bitwise, Memory, and Kernel ROM). It is implemented using multiset checks. +- The range checker bus [$b_{range}$](../range.md#communication-bus), which facilitates requests between the [stack](../stack/u32_ops.md) and [memory](../chiplets/memory.md) components and the [range checker](../range.md). It is implemented using LogUp. + + +## Length of auxiliary columns for lookup arguments + +The auxiliary columns used for buses and virtual tables are computed by including information from the *current* row of the main execution trace into the *next* row of the auxiliary trace column. Thus, in order to ensure that the trace is long enough to give the auxiliary column space for its final value, a padding row may be required at the end of the trace of the component upon which the auxiliary column depends. + +This is true when the data in the main trace could go all the way to the end of the trace, such as in the case of the range checker. + +## Cost of auxiliary columns for lookup arguments +It is important to note that depending on the field in which we operate, an auxiliary column implementing a lookup argument may actually require more than one trace column. This is specifically true for small fields. + +Since Miden uses a 64-bit field, each auxiliary column needs to be represented by $2$ columns to achieve ~100-bit security and by $3$ columns to achieve ~128-bit security. diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/design/lookups/logup.md b/versioned_docs/version-0.12 (stable)/miden-vm/design/lookups/logup.md new file mode 100644 index 0000000..9e360f3 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-vm/design/lookups/logup.md @@ -0,0 +1,70 @@ +--- +title: "LogUp: Multivariate Lookups with Logarithmic Derivatives" +sidebar_position: 3 +--- + +# LogUp: multivariate lookups with logarithmic derivatives + +The description of LogUp can be found [here](https://eprint.iacr.org/2022/1530.pdf). In MidenVM, LogUp is used to implement efficient [communication buses](./index.md#communication-buses-in-miden-vm). + +Using the LogUp construction instead of a simple [multiset check](./multiset.md) with running products reduces the computational effort for the prover and the verifier. Given two columns $a$ and $b$ in the main trace where $a$ contains duplicates and $b$ does not (i.e. $b$ is part of the lookup table), LogUp allows us to compute two logarithmic derivatives and check their equality. + +$$ +\sum_{i=0}^{l} \frac{1}{(\alpha - a_i)} = \sum_{i=0}^{n} \frac{m_i}{(\alpha - b_i)} +$$ + +In the above: +- $l$ is the number of values in $a$, which must be smaller than the size of the field. (The prime field used for Miden VM has modulus $p = 2^{64} - 2^{32} + 1$, so $l < p$ must be true.) +- $n$ is the number of values in $b$, which must be smaller than the size of the field. ($n < p$, for Miden VM) +- $m_i$ is the multiplicity of $b_i$, which is expected to match the number of times the value $b_i$ is duplicated in column $a$. It must be smaller than the size of the set of lookup values. ($m_i < n$) +- $\alpha$ is a random value that is sent to the prover by the verifier after the prover commits to the execution trace of the program. + +Thus, instead of needing to compute running products, we are able to assert correct lookups by computing running sums. + +## Usage in Miden VM + +The generalized trace columns and constraints for this construction are as follows, where component $X$ is some component in the trace and lookup table $T$ contains the values $v$ which need to be looked up from $X$ and how many times they are looked up (the multiplicity $m$). + +![logup_component_x](../../img/design/lookups/logup_component.png) + +![logup_table_t](../../img/design/lookups/logup_table.png) + +### Constraints + +The diagrams above show running sum columns for computing the logarithmic derivatives for both $X$ and $T$. As an optimization, we can combine these values into a single auxiliary column in the extension field that contains the running sum of values from both logarithmic derivatives. We'll refer to this column as a _communication bus_ $b$, since it communicates the lookup request from the component $X$ to the lookup table $T$. + +This can be expressed as follows: + +> $$ +> b' = b + \frac{m}{(\alpha - v)} - \frac{1}{(\alpha - x)} +> $$ + +Since constraints must be expressed without division, the actual constraint which is enforced will be the following: + +> $$ +> b' \cdot (\alpha - v) \cdot (\alpha - x) = b \cdot (\alpha - x) \cdot (\alpha - v) + m \cdot (\alpha - x) - (\alpha - v) \text{ | degree} = 3 +> $$ + +In general, we will write constraints within these docs using the previous form, since it's clearer and more readable. + +Additionally, boundary constraints must be enforced against $b$ to ensure that its initial and final values are $1$. This will enforce that the logarithmic derivatives for $X$ and $T$ were equal. + +### Extending the construction to multiple components + +The functionality of the bus can easily be extended to receive lookup requests from multiple components. For example, to additionally support requests from column $y$, the bus constraint would be modified to the following: + +> $$ +> b' = b + \frac{m}{(\alpha - v)} - \frac{1}{(\alpha - x)} - \frac{1}{(\alpha - y)} \text{ | degree} = 4 +> $$ + +Since the maximum constraint degree in Miden VM is 9, the lookup table $T$ could accommodate requests from at most 7 trace columns in the same trace row via this construction. + +### Extending the construction with flags + +Boolean flags can also be used to determine when requests from various components are sent to the bus. For example, let $f_x$ be 1 when a request should be sent from $x$ and 0 otherwise, and let $f_y$ be similarly defined for column $y$. We can use the following constraint to turn requests on or off: + +> $$ +> b' = b + \frac{m}{(\alpha - v)} - \frac{f_x}{(\alpha - x)} - \frac{f_y}{(\alpha - y)} \text{ | degree} = 4 +> $$ + +If any of these flags have degree greater than 2 then this will increase the overall degree of the constraint and reduce the number of lookup requests that can be accommodated by the bus per row. diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/design/lookups/multiset.md b/versioned_docs/version-0.12 (stable)/miden-vm/design/lookups/multiset.md new file mode 100644 index 0000000..d5f1705 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-vm/design/lookups/multiset.md @@ -0,0 +1,111 @@ +--- +title: "Multiset Checks" +sidebar_position: 2 +--- + +# Multiset checks + +A brief introduction to multiset checks can be found [here](https://hackmd.io/@relgabizon/ByFgSDA7D). In Miden VM, multiset checks are used to implement [virtual tables](#virtual-tables) and efficient [communication buses](./index.md#communication-buses-in-miden-vm). + +## Running product columns +Although the multiset equality check can be thought of as comparing multiset equality between two vectors $a$ and $b$, in Miden VM it is implemented as a single running product column in the following way: + +- The running product column is initialized to a value $x$ at the beginning of the trace. (We typically use $x = 1$.) +- All values of $a$ are multiplied into the running product column. +- All values of $b$ are divided out of the running product column. +- If $a$ and $b$ were multiset equal, then the running product column will equal $x$ at the end of the trace. + +Running product columns are computed using a set of random values $\alpha_0$, $\alpha_1, ...$ sent to the prover by the verifier after the prover commits to the execution trace of the program. + +## Virtual tables + +Virtual tables can be used to store intermediate data which is computed at one cycle and used at a different cycle. When the data is computed, the row is added to the table, and when it is used later, the row is deleted from the table. Thus, all that needs to be proved is the data consistency between the row that was added and the row that was deleted. + +The consistency of a virtual table can be proved with a single trace column $p$, which keeps a running product of rows that were inserted into and deleted from the table. This is done by reducing each row to a single value, multiplying the value into $p$ when the row is inserted, and dividing the value out of $p$ when the row is removed. Thus, at any step of the computation, $p$​ will contain a product of all rows currently in the table. + +The initial value of $p$​ is set to 1. Thus, if the table is empty by the time Miden VM finishes executing a program (we added and then removed exactly the same set of rows), the final value of $p$​ will also be equal to 1. The initial and final values are enforced via boundary constraints. + +### Computing a virtual table's trace column + +To compute a product of rows, we'll first need to reduce each row to a single value. This can be done as follows. + +Let $t_0, t_1, t_2, ...$ be columns in the virtual table, and assume the verifier sends a set of random values $\alpha_0$, $\alpha_1, ...$ to the prover after the prover commits to the execution trace of the program. + +The prover reduces row $i$ in the table to a single value $r_i$ as: + +$$ +r_i = \alpha_0 + \alpha_1 \cdot t_{0, i} + \alpha_2 \cdot t_{1, i} + \alpha_3 \cdot t_{2, i} + ... +$$ + +Then, when row $i$ is added to the table, we'll update the value in the $p$ column like so: + +$$ +p' = p \cdot r_i +$$ + +Analogously, when row $i$ is removed from the table, we'll update the value in column $p$ like so: + +$$ +p' = \frac{p}{r_i} +$$ + +### Virtual tables in Miden VM + +Miden VM makes use of 6 virtual tables across 4 components: + +- Stack: + - [Overflow table](../stack/index.md#overflow-table) +- Decoder: + - [Block stack table](../decoder/index.md#block-stack-table) + - [Block hash table](../decoder/index.md#block-hash-table) + - [Op group table](../decoder/index.md#op-group-table) +- Chiplets: + - [Chiplets virtual table](../chiplets/index.md#chiplets-virtual-table), which combines the following two tables into one: + - [Hash chiplet sibling table](../chiplets/hasher.md#sibling-table-constraints) + - [Kernel ROM chiplet procedure table](../chiplets/kernel_rom.md#constraints) + +## Communication buses {#communication-buses} + +A `bus` can be implemented as a single trace column $b$ where a request can be sent to a specific component and a corresponding response will be sent back by that component. + +The values in this column contain a running product of the communication with the component as follows: + +- Each request is “sent” by computing a lookup value from some information that's specific to the specialized component, the operation inputs, and the operation outputs, and then dividing it out of the running product column $b$. +- Each chiplet response is “sent” by computing the same lookup value from the component-specific information, inputs, and outputs, and then multiplying it into the running product column $b$. + +Thus, if the requests and responses match, and the bus column $b$ is initialized to $1$, then $b$ will start and end with the value $1$. This condition is enforced by boundary constraints on column $b$. + +Note that the order of the requests and responses does not matter, as long as they are all included in $b$. In fact, requests and responses for the same operation will generally occur at different cycles. Additionally, there could be multiple requests sent in the same cycle, and there could also be a response provided at the same cycle that a request is received. + +### Communication bus constraints + +These constraints can be expressed in a general way with the 2 following requirements: + +- The lookup value must be computed using random values $\alpha_0, \alpha_1$, etc. that are provided by the verifier after the prover has committed to the main execution trace. +- The lookup value must include all uniquely identifying information for the component/operation and its inputs and outputs. + +Given an example operation $op_{ex}$ with inputs $i_0, ..., i_n$ and outputs $o_0, ..., o_m$, the lookup value can be computed as follows: + +$$lookup = \alpha_0 + \alpha_1 \cdot op_{ex} + \alpha_2 \cdot i_0 + ... + \alpha_{n+2} \cdot i_n + \alpha_{n+3} \cdot o_0 + ... + \alpha_{n + 2 + m} \cdot o_m$$ + +The constraint for sending this to the bus as a request would be: + +$$b' \cdot lookup = b$$ + +The constraint for sending this to the bus as a response would be: + +$$b' = b \cdot lookup$$ + +However, these constraints must be combined, since it's possible that requests and responses both occur during the same cycle. + +To combine them, let $u_{lookup}$ be the request value and let $v_{lookup}$ be the response value. These values are both computed the same way as shown above, but the data sources are different, since the input/output values used to compute $u_{lookup}$ come from the trace of the component that's "offloading" the computation, while the input/output values used to compute $v_{lookup}$ come from the trace of the specialized component. + +The final constraint can be expressed as: + +$$b' \cdot u_{lookup} = b \cdot v_{lookup}$$ + +### Communication buses in Miden VM + +In Miden VM, the specialized components are implemented as dedicated segments of the execution trace, which include the 3 chiplets in the Chiplets module (the hash chiplet, bitwise chiplet, and memory chiplet). + +Miden VM currently uses multiset checks to implement the chiplets bus [$b_{chip}$](../chiplets/index.md#chiplets-bus), which communicates with all of the chiplets (Hash, Bitwise, and Memory). diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/design/programs.md b/versioned_docs/version-0.12 (stable)/miden-vm/design/programs.md new file mode 100644 index 0000000..2318156 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-vm/design/programs.md @@ -0,0 +1,135 @@ +--- +title: "Programs in Miden VM" +sidebar_position: 2 +--- + +# Programs in Miden VM +Miden VM consumes programs in a form of a Merkelized Abstract Syntax Tree (MAST). This tree is a binary tree where each node is a *code block*. The VM starts execution at the root of the tree, and attempts to recursively execute each required block according to its semantics. If the execution of a code block fails, the VM halts at that point and no further blocks are executed. A set of currently available blocks and their execution semantics are described below. + +## Code blocks + +### Join block +A **join** block is used to describe sequential execution. When the VM encounters a *join* block, it executes its left child first, and then executes its right child. + +![join_block](../img/design/programs/join_block.png) + +A *join* block must always have two children, and thus, cannot be a leaf node in the tree. + +### Split block +A **split** block is used to describe conditional execution. When the VM encounters a *split* block, it checks the top of the stack. If the top of the stack is $1$, it executes the left child, if the top of the stack is $0$, it executes the right child. If the top of the stack is neither $0$ nor $1$, the execution fails. + +![split_block](../img/design/programs/split_block.png) + +A *split* block must always have two children, and thus, cannot be a leaf node in the tree. + +### Loop block +A **loop** block is used to describe condition-based iterative execution. When the VM encounters a *loop* block, it checks the top of the stack. If the top of the stack is $1$, it executes the loop body, if the top of the stack is $0$, the block is not executed. If the top of the stack is neither $0$ nor $1$, the execution fails. + +After the body of the loop is executed, the VM checks the top of the stack again. If the top of the stack is $1$, the body is executed again, if the top of the stack is $0$, the loop is exited. If the top of the stack is neither $0$ nor $1$, the execution fails. + +![loop_block](../img/design/programs/loop_block.png) + +A *loop* block must always have one child, and thus, cannot be a leaf node in the tree. + +### Dyn block +A **dyn** block is used to describe a node whose target is specified dynamically via the stack. When the VM encounters a *dyn* block, it executes a program which hashes to the target specified by the top of the stack. Thus, it has a dynamic target rather than a hardcoded target. In order to execute a *dyn* block, the VM must be aware of a program with the hash value that is specified by the top of the stack. Otherwise, the execution fails. + +![dyn_block](../img/design/programs/dyn_block.png) + +A *dyn* block must always have one (dynamically-specified) child. Thus, it cannot be a leaf node in the tree. + +### Call block + +A **call** block is used to describe a function call which is executed in a [user context](../user_docs/assembly/execution_contexts.md). When the VM encounters a *call* block, it creates a new user context, then executes a program which hashes to the target specified by the *call* block in the new context. Thus, in order to execute a *call* block, the VM must be aware of a program with the specified hash. Otherwise, the execution fails. At the end of the *call* block, execution returns to the previous context. + + +When executing a *call* block, the VM does the following: +1. Checks if a *syscall* is already being executed and fails if so. +2. Sets the depth of the stack to 16. +3. Upon return, checks that the depth of the stack is 16. If so, the original stack depth is restored. Otherwise, an error occurs. + +![call_block](../img/design/programs/call_block.png) + +A *call* block does not have any children. Thus, it must be leaf node in the tree. + +### Syscall block + +A **syscall** block is used to describe a function call which is executed in the [root context](../user_docs/assembly/execution_contexts.md). When the VM encounters a *syscall* block, it returns to the root context, then executes a program which hashes to the target specified by the *syscall* block. Thus, in order to execute a *syscall* block, the VM must be aware of a program with the specified hash, and that program must belong to the kernel against which the code is compiled. Otherwise, the execution fails. At the end of the *syscall* block, execution returns to the previous context. + +When executing a *syscall* block, the VM does the following: +1. Checks if a *syscall* is already being executed and fails if so. +2. Sets the depth of the stack to 16. +3. Upon return, checks that the depth of the stack is 16. If so, the original stack depth is restored. Otherwise, an error occurs. + +![syscall_block](../img/design/programs/syscall_block.png) + +A *syscall* block does not have any children. Thus, it must be leaf node in the tree. + +### Basic block +A **basic** block is used to describe a linear sequence of operations. When the VM encounters a *basic* block, it breaks the sequence of operations into batches and groups according to the following rules: +* A group is represented by a single field element. Thus, assuming a single operation can be encoded using 7 bits, and assuming we are using a 64-bit field, a single group may encode up to 9 operations or a single immediate value. +* A batch is a set of groups which can be absorbed by a hash function used by the VM in a single permutation. For example, assuming the hash function can absorb up to 8 field elements in a single permutation, a single batch may contain up to 8 groups. +* There is no limit on the number of batches contained within a single basic block. + +Thus, for example, executing 8 pushes in a row will result in two operation batches as illustrated in the picture below: + +![span_block_creation](../img/design/programs/span_block_creation.png) + +* The first batch will contain 8 groups, with the first group containing 7 `PUSH` opcodes and 1 `NOOP`, and the remaining 7 groups containing immediate values for each of the push operations. The reason for the `NOOP` is explained later in this section. +* The second batch will contain 2 groups, with the first group containing 1 `PUSH` opcode and 1 `NOOP`, and the second group containing the immediate value for the last push operation. + + +If a sequence of operations does not have any operations which carry immediate values, up to 72 operations can fit into a single batch. + +From the user's perspective, all operations are executed in order, however, the VM may insert occasional `NOOP`s to ensure proper alignment of all operations in the sequence. Currently, the alignment requirements are as follows: +* An operation carrying an immediate value cannot be the last operation in a group. Thus, for example, if a `PUSH` operation is the last operation in a group, the VM will insert a `NOOP` after it. + +A *basic* block does not have any children, and thus, must be leaf node in the tree. + +## Program example +Consider the following program, where $a_0, ..., a_i$, $b_0, ..., b_j$ etc. represent individual operations: + +``` +a_0, ..., a_i +if.true + b_0, ..., b_j +else + c_0, ..., c_k + while.true + d_0, ..., d_n + end + e_0, ..., e_m +end +f_0, ..., f_l +``` + +A MAST for this program would look as follows: + +![mast_of_program](../img/design/programs/mast_of_program.png) + +Execution of this program would proceed as follows: + +1. The VM will start execution at the root of the program which is block $B_5$. +2. Since, $B_5$ is a *join block*, the VM will attempt to execute block $B_4$ first, and only after that execute block $f$. +3. Block $B_4$ is also a *join block*, and thus, the VM will execute block $a$ by executing operations $a_0, ..., a_i$ in sequence, and then execute block $B_3$. +4. Block $B_3$ is a *split block*, and thus, the VM will pop the value off the top of the stack. If the popped value is $1$, operations from block $b$ will be executed in sequence. If the popped value is $0$, then the VM will attempt to execute block $B_2$. +5. $B_2$ is a *join block*, thus, the VM will try to execute block $B_1$ first, and then execute operations from block $e$. +6. Block $B_1$ is also a *join_block*, and thus, the VM will first execute all operations in block $c$, and then will attempt to execute block $B_0$. +7. Block $B_0$ is a loop block, thus, the VM will pop the value off the top of the stack. If the pooped value is $1$, the VM will execute the body of the loop defined by block $d$. If the popped value is $0$, the VM will not execute block $d$ and instead will move up the tree executing first block $e$, then $f$. +8. If the VM does enter the loop, then after operation $d_n$ is executed, the VM will pop the value off the top of the stack again. If the popped value is $1$, the VM will execute block $d$ again, and again until the top of the stack becomes $0$. Once the top of the stack becomes $0$, the VM will exit the loop and will move up the tree executing first block $e$, then $f$. + +## Program hash computation +Every Miden VM program can be reduced to a unique hash value. Specifically, it is infeasible to find two Miden VM programs with distinct semantics which hash to the same value. Padding a program with `NOOP`s does not change a program's execution semantics, and thus, programs which differ only in the number and/or placement of `NOOP`s may hash to the same value, although in most cases padding with `NOOP` should not affect program hash. + +To prevent program hash collisions we implement domain separation across the variants of control blocks. We define the domain value to be the opcode of the operation that initializes the control block. + +Below we denote $hash$ to be an arithmetization-friendly hash function with $4$-element output and capable of absorbing $8$ elements in a single permutation. The hash domain is specified as the subscript of the hash function and its value is used to populate the second capacity register upon initialization of control block hashing - $hash_{domain}(a, b)$. + +* The hash of a **join** block is computed as $hash_{join}(a, b)$, where $a$ and $b$ are hashes of the code block being joined. +* The hash of a **split** block is computed as $hash_{split}(a, b)$, where $a$ is a hash of a code block corresponding to the *true* branch of execution, and $b$ is a hash of a code block corresponding to the *false branch* of execution. +* The hash of a **loop** block is computed as $hash_{loop}(a, 0)$, where $a$ is a hash of a code block corresponding to the loop body. +* The hash of a **dyn** block is set to a constant, so it is the same for all *dyn* blocks. It does not depend on the hash of the dynamic child. This constant is computed as the RPO hash of two empty words (`[ZERO, ZERO, ZERO, ZERO]`) using a domain value of `DYN_DOMAIN`, where `DYN_DOMAIN` is the op code of the `Dyn` operation. +* The hash of a **call** block is computed as $hash_{call}(a, 0)$, where $a$ is a hash of a program of which the VM is aware. +* The hash of a **syscall** block is computed as $hash_{syscall}(a, 0)$, where $a$ is a hash of a program belonging to the kernel against which the code was compiled. +* The hash of a **basic** block is computed as $hash(a_1, ..., a_k)$, where $a_i$ is the $i$th batch of operations in the *basic* block. Each batch of operations is defined as containing $8$ field elements, and thus, hashing a $k$-batch *basic* block requires $k$ absorption steps. + * In cases when the number of operations is insufficient to fill the last batch entirely, `NOOPs` are appended to the end of the last batch to ensure that the number of operations in the batch is always equal to $8$. diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/design/range.md b/versioned_docs/version-0.12 (stable)/miden-vm/design/range.md new file mode 100644 index 0000000..4fcf9a4 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-vm/design/range.md @@ -0,0 +1,177 @@ +--- +title: "Range Checker" +sidebar_position: 4 +--- + +# Range Checker + +Miden VM relies very heavily on 16-bit range-checks (checking if a value of a field element is between $0$ and $2^{16}$). For example, most of the [u32 operations](./stack/u32_ops.md) need to perform between two and four 16-bit range-checks per operation. Similarly, operations involving memory (e.g. load and store) require two 16-bit range-checks per operation. + +Thus, it is very important for the VM to be able to perform a large number of 16-bit range checks very efficiently. In this note we describe how this can be achieved using the [LogUp](./lookups/logup.md) lookup argument. + +## 8-bit range checks + +First, let's define a construction for the simplest possible 8-bit range-check. This can be done with a single column as illustrated below. + +![rc_8_bit_range_check](../img/design/range/rc_8_bit_range_check.png) + +For this to work as a range-check we need to enforce a few constraints on this column: + +- The value in the first row must be $0$. +- The value in the last row must be $255$. +- As we move from one row to the next, we can either keep the value the same or increment it by $1$. + +Denoting $v$ as the value of column $v$ in the current row, and $v'$ as the value of column $v$ in the next row, we can enforce the last condition as follows: + +$$ +(v' - v) \cdot (v' - v - 1) = 0 +$$ + +Together, these constraints guarantee that all values in column $v$ are between $0$ and $255$ (inclusive). + +We can then make use of the LogUp lookup argument by adding another column $b$ which will keep a running sum that is the logarithmic derivative of the product of values in the $v$ column. The transition constraint for $b$ would look as follows: + +$$ +b' = b + \frac{1}{(\alpha - v)} +$$ + +Since constraints cannot include divisions, the constraint would actually be expressed as the following degree 2 constraint: + +$$ +b' \cdot (\alpha - v) = b \cdot (\alpha - v) + 1 +$$ + +Using these two columns we can check if some other column in the execution trace is a permutation of values in $v$. Let's call this other column $x$. We can compute the logarithmic derivative for $x$ as a running sum in the same way as we compute it for $v$. Then, we can check that the last value in $b$ is the same as the final value for the running sum of $x$. + +While this approach works, it has a couple of limitations: + +- First, column $v$ must contain all values between $0$ and $255$. Thus, if column $x$ does not contain one of these values, we need to artificially add this value to $x$ somehow (i.e., we need to pad $x$ with extra values). +- Second, assuming $n$ is the length of execution trace, we can range-check at most $n$ values. Thus, if we wanted to range-check more than $n$ values, we'd need to introduce another column similar to $v$. + +We can get rid of both requirements by including the _multiplicity_ of the value $v$ into the calculation of the logarithmic derivative for LogUp, which will allow us to specify exactly how many times each value needs to be range-checked. + +### A better construction + +Let's add one more column $m$ to our table to keep track of how many times each value should be range-checked. + +![rc_8_bit_logup](../img/design/range/rc_8_bit_logup.png) + +The transition constraint for $b$ is now as follows: + +$$ +b' = b + \frac{m}{(\alpha - v)} +$$ + +This addresses the limitations we had as follows: +1. We no longer need to pad the column we want to range-check with extra values because we can skip the values we don't care about by setting the multiplicity to $0$. +2. We can range check as many unique values as there are rows in the trace, and there is essentially no limit to how many times each of these values can be range-checked. (The only restriction on the multiplicity value is that it must be less than the size of the set of lookup values. Therefore, for long traces where $n > 2^{16}$, $m < 2^{16}$ must hold, and for short traces $m < n$ must be true.) + +Additionally, the constraint degree has not increased versus the naive approach, and the only additional cost is a single trace column. + +## 16-bit range checks + +To support 16-bit range checks, let's try to extend the idea of the 8-bit table. Our 16-bit table would look like so (the only difference is that column $v$ now has to end with value $65535$): + +![rc_16_bit_logup](../img/design/range/rc_16_bit_logup.png) + +While this works, it is rather wasteful. In the worst case, we'd need to enumerate over 65K values, most of which we may not actually need. It would be nice if we could "skip over" the values that we don't want. One way to do this could be to add bridge rows between two values to be range checked and add constraints to enforce the consistency of the gap between these bridge rows. + +If we allow gaps between two consecutive rows to only be 0 or powers of 2, we could enforce a constraint: + +$$ +\Delta v \cdot (\Delta v - 1) \cdot (\Delta v - 2) \cdot (\Delta v - 4) \cdot (\Delta v - 8) \cdot (\Delta v - 16) \cdot (\Delta v - 32) \cdot (\Delta v - 64) \cdot (\Delta v - 128) = 0 +$$ + +This constraint has a degree 9. This construction allows the minimum trace length to be 1024. + +We could go even further and allow the gaps between two consecutive rows to only be 0 or powers of 3. In this case we would enforce the constraint: + +$$ +\Delta v \cdot (\Delta v - 1) \cdot (\Delta v - 3) \cdot (\Delta v - 9) \cdot (\Delta v - 27) \cdot (\Delta v - 81) \cdot (\Delta v - 243) \cdot (\Delta v - 729) \cdot (\Delta v - 2187) = 0 +$$ + +This allows us to reduce the minimum trace length to 64. + +To find out the number of bridge rows to be added in between two values to be range checked, we represent the gap between them as a linear combination of powers of 3, ie, + +$$ +(r' - r) = \sum_{i=0}^{7} x_i \cdot 3^i +$$ + +Then for each $x_i$ except the first, we add a bridge row at a gap of $3^i$. + +## Miden approach + +This construction is implemented in Miden with the following requirements, capabilities and constraints. + +### Requirements + +- 2 columns of the main trace: $m, v$, where $v$ contains the value being range-checked and $m$ is the number of times the value is checked (its multiplicity). +- 1 [bus](./lookups/index.md#communication-buses-in-miden-vm) $b_{range}$ to ensure that the range checks performed in the range checker match those requested by other VM components (the [stack](./stack/u32_ops.md#range-checks) and the [memory chiplet](./chiplets/memory.md)). + +### Capabilities + +The construction gives us the following capabilities: +- For long traces (when $n > 2^{16}$), we can do an essentially unlimited number of arbitrary 16-bit range-checks. +- For short traces ($2^5 < n \le 2^{16}$), we can range-check slightly fewer than $n$ unique values, but there is essentially no practical limit to the total number of range checks. + + +### Execution trace + +The range checker's execution trace looks as follows: + +![rc_with_bridge_rows.png](../img/design/range/rc_with_bridge_rows.png) + +The columns have the following meanings: +- $m$ is the multiplicity column that indicates the number of times the value in that row should be range checked (included into the computation of the logarithmic derivative). +- $v$ contains the values to be range checked. + - These values go from $0$ to $65535$. Values must either stay the same or increase by powers of 3 less than or equal to $3^7$. + - The final 2 rows of the 16-bit section of the trace must both equal $65535$. The extra value of $65535$ is required in order to [pad the trace](./lookups/index.md#length-of-auxiliary-columns-for-lookup-arguments) so the [$b_{range}$](#communication-bus) bus column can be computed correctly. + +### Execution trace constraints + +First, we need to constrain that the consecutive values in the range checker are either the same or differ by powers of 3 that are less than or equal to $3^7$. + +> $$ +> \Delta v \cdot (\Delta v - 1) \cdot (\Delta v - 3) \cdot (\Delta v - 9) \cdot (\Delta v - 27) \cdot (\Delta v - 81) +> \cdot (\Delta v - 243) \cdot (\Delta v - 729) \cdot (\Delta v - 2187) = 0 \text{ | degree} = 9 +> $$ + +In addition to the transition constraints described above, we also need to enforce the following boundary constraints: + +- The value of $v$ in the first row is $0$. +- The value of $v$ in the last row is $65535$. + +### Communication bus + +$b_{range}$ is the [bus](./lookups/index.md#communication-buses-in-miden-vm) that connects components which require 16-bit range checks to the values in the range checker. The bus constraints are defined by the components that use it to communicate. + +Requests are sent to the range checker bus by the following components: +- The Stack sends requests for 16-bit range checks during some [`u32` operations](./stack/u32_ops.md#range-checks). +- The [Memory chiplet](./chiplets/memory.md) sends requests for 16-bit range checks against the values in the $d_0$ and $d_1$ trace columns to enforce internal consistency. + +Responses are provided by the range checker using the transition constraint for the LogUp construction described above. + +> $$ +> b'_{range} = b_{range} + \frac{m}{(\alpha - v)} \text{ | degree} = 2 +> $$ + +To describe the complete transition constraint for the bus, we'll define the following variables: + +- $f_{stack}$: the boolean flag that indicates whether or not a stack operation requiring range checks is occurring. This flag has degree 3. +- $f_{mem}$: the boolean flag that indicates whether or not a memory operation requiring range checks is occurring. This flag has degree 3. +- $s_0, s_1, s_2, s_3$: the values for which range checks are requested from the stack when $f_{stack}$ is set. +- $m_0, m_1$: the values for which range checks are requested from the memory chiplet when $f_{mem}$ is set. + +> $$ +> b'_{range} = b_{range} + \frac{m}{(\alpha - v)} - \frac{f_{stack}}{(\alpha - s_0)} - \frac{f_{stack}}{(\alpha - s_1)} - \frac{f_{stack}}{(\alpha - s_2)} - \frac{f_{stack}}{(\alpha - s_3)} - \frac{f_{mem}}{(\alpha - m_0)} - \frac{f_{mem}}{(\alpha - m_1)} \text{ | degree} = 9 +> $$ + +As previously mentioned, constraints cannot include divisions, so the actual constraint which is applied will be the equivalent expression in which all denominators have been multiplied through, which is degree 9. + +If $b_{range}$ is initialized to $1$ and the values sent to the bus by other VM components match those that are range-checked in the trace, then at the end of the trace we should end up with $b_{range} = 1$. + +Therefore, in addition to the transition constraint described above, we also need to enforce the following boundary constraints: + +- The value of $b_{range}$ in the first row $1$. +- The value of $b_{range}$ in the last row $1$. diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/design/stack/_category_.yml b/versioned_docs/version-0.12 (stable)/miden-vm/design/stack/_category_.yml new file mode 100644 index 0000000..246c4af --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-vm/design/stack/_category_.yml @@ -0,0 +1,4 @@ +label: "Operand stack" +# Determines where this documentation section appears relative to other sections in the parent folder +position: 3 +collapsed: true diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/design/stack/crypto_ops.md b/versioned_docs/version-0.12 (stable)/miden-vm/design/stack/crypto_ops.md new file mode 100644 index 0000000..c6f12b4 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-vm/design/stack/crypto_ops.md @@ -0,0 +1,420 @@ +--- +title: "Cryptographic Operations" +sidebar_position: 8 +--- + +# Cryptographic operations +In this section we describe the AIR constraints for Miden VM cryptographic operations. + +Cryptographic operations in Miden VM are performed by the [Hash chiplet](../chiplets/hasher.md). Communication between the stack and the hash chiplet is accomplished via the chiplet bus $b_{chip}$. To make requests to and to read results from the chiplet bus we need to divide its current value by the value representing the request. + +Thus, to describe AIR constraints for the cryptographic operations, we need to define how to compute these input and output values within the stack. We do this in the following sections. + +## HPERM +The `HPERM` operation applies Rescue Prime Optimized permutation to the top $12$ elements of the stack. The stack is assumed to be arranged so that the $8$ elements of the rate are at the top of the stack. The capacity word follows, with the number of elements to be hashed at the deepest position in stack. The diagram below illustrates this graphically. + +![hperm](../../img/design/stack/crypto_ops/HPERM.png) + +In the above, $r$ (located in the helper register $h_0$) is the row address from the hash chiplet set by the prover non-deterministically. + +For the `HPERM` operation, we define input and output values as follows: + +$$ +v_{input} = \alpha_0 + \alpha_1 \cdot op_{linhash} + \alpha_2 \cdot h_0 + \sum_{j=0}^{11} (\alpha_{j+4} \cdot s_{11-j}) +$$ + +$$ +v_{output} = \alpha_0 + \alpha_1 \cdot op_{retstate} + \alpha_2 \cdot (h_0 + 7) + \sum_{j=0}^{11} (\alpha_{j+4} \cdot s_{11-j}') +$$ + +In the above, $op_{linhash}$ and $op_{retstate}$ are the unique [operation labels](../chiplets/index.md#operation-labels) for initiating a linear hash and reading the full state of the hasher respectively. Also note that the term for $\alpha_3$ is missing from the above expressions because for Rescue Prime Optimized permutation computation the index column is expected to be set to $0$. + +Using the above values, we can describe the constraint for the chiplet bus column as follows: + +$$ +b_{chip}' \cdot v_{input} \cdot v_{output} = b_{chip} \text{ | degree} = 3 +$$ + +The above constraint enforces that the specified input and output rows must be present in the trace of the hash chiplet, and that they must be exactly $7$ rows apart. + +The effect of this operation on the rest of the stack is: +* **No change** starting from position $12$. + +## MPVERIFY +The `MPVERIFY` operation verifies that a Merkle path from the specified node resolves to the specified root. This operation can be used to prove that the prover knows a path in the specified Merkle tree which starts with the specified node. + +Prior to the operation, the stack is expected to be arranged as follows (from the top): +- Value of the node, 4 elements ($V$ in the below image) +- Depth of the path, 1 element ($d$ in the below image) +- Index of the node, 1 element ($i$ in the below image) +- Root of the tree, 4 elements ($R$ in the below image) + +The Merkle path itself is expected to be provided by the prover non-deterministically (via the advice provider). If the prover is not able to provide the required path, the operation fails. Otherwise, the state of the stack does not change. The diagram below illustrates this graphically. + +![mpverify](../../img/design/stack/crypto_ops/MPVERIFY.png) + +In the above, $r$ (located in the helper register $h_0$) is the row address from the hash chiplet set by the prover non-deterministically. + +For the `MPVERIFY` operation, we define input and output values as follows: + +$$ +v_{input} = \alpha_0 + \alpha_1 \cdot op_{mpver} + \alpha_2 \cdot h_0 + \alpha_3 \cdot s_5 + \sum_{j=0}^3 \alpha_{j+8} \cdot s_{3 - j} +$$ + +$$ +v_{output} = \alpha_0 + \alpha_1 \cdot op_{rethash} + \alpha_2 \cdot (h_0 + 8 \cdot s_4 - 1) + \sum_{j=0}^3\alpha_{j + 8} \cdot s_{9 - j} +$$ + +In the above, $op_{mpver}$ and $op_{rethash}$ are the unique [operation labels](../chiplets/index.md#operation-labels) for initiating a Merkle path verification computation and reading the hash result respectively. The sum expression for inputs computes the value of the leaf node, while the sum expression for the output computes the value of the tree root. + +Using the above values, we can describe the constraint for the chiplet bus column as follows: + +$$ +b_{chip}' \cdot v_{input} \cdot v_{output} = b_{chip} \text{ | degree} = 3 +$$ + +The above constraint enforces that the specified input and output rows must be present in the trace of the hash chiplet, and that they must be exactly $8 \cdot d - 1$ rows apart, where $d$ is the depth of the node. + +The effect of this operation on the rest of the stack is: +* **No change** starting from position $0$. + +## MRUPDATE +The `MRUPDATE` operation computes a new root of a Merkle tree where a node at the specified position is updated to the specified value. + +The stack is expected to be arranged as follows (from the top): +- old value of the node, 4 element ($V$ in the below image) +- depth of the node, 1 element ($d$ in the below image) +- index of the node, 1 element ($i$ in the below image) +- current root of the tree, 4 elements ($R$ in the below image) +- new value of the node, 4 element ($NV$ in the below image) + +The Merkle path for the node is expected to be provided by the prover non-deterministically (via merkle sets). At the end of the operation, the old node value is replaced with the new root value computed based on the provided path. Everything else on the stack remains the same. The diagram below illustrates this graphically. + +![mrupdate](../../img/design/stack/crypto_ops/MRUPDATE.png) + +In the above, $r$ (located in the helper register $h_0$) is the row address from the hash chiplet set by the prover non-deterministically. + +For the `MRUPDATE` operation, we define input and output values as follows: + +$$ +v_{inputold} = \alpha_0 + \alpha_1 \cdot op_{mruold} + \alpha_2 \cdot h_0 + \alpha_3 \cdot s_5 + \sum_{j=0}^3\alpha_{j + 8} \cdot s_{3 - j} +$$ + +$$ +v_{outputold} = \alpha_0 + \alpha_1 \cdot op_{rethash} + \alpha_2 \cdot (h_0 + 8 \cdot s_4 - 1) + \sum_{j=0}^3\alpha_{j + 8} \cdot s_{9 - j} +$$ + +$$ +v_{inputnew} = \alpha_0 + \alpha_1 \cdot op_{mrunew} + \alpha_2 \cdot (h_0 + 8 \cdot s_4) + \alpha_3 \cdot s_5 + \sum_{j=0}^3\alpha_{j + 8} \cdot s_{13 - j} +$$ + +$$ +v_{outputnew} = \alpha_0 + \alpha_1 \cdot op_{rethash} + \alpha_2 \cdot (h_0 + 2 \cdot 8 \cdot s_4 - 1) + \sum_{j=0}^3\alpha_{j + 8} \cdot s_{3 - j}' +$$ + +In the above, the first two expressions correspond to inputs and outputs for verifying the Merkle path between the old node value and the old tree root, while the last two expressions correspond to inputs and outputs for verifying the Merkle path between the new node value and the new tree root. The hash chiplet ensures the same set of sibling nodes are used in both of these computations. + +The $op_{mruold}$, $op_{mrunew}$, and $op_{rethash}$ are the unique [operation labels](../chiplets/index.md#operation-labels) used by the above computations. + +> $$ +> b_{chip}' \cdot v_{inputold} \cdot v_{outputold} \cdot v_{inputnew} \cdot v_{outputnew} = b_{chip} \text{ | degree} = 5 +> $$ + +The above constraint enforces that the specified input and output rows for both, the old and the new node/root combinations, must be present in the trace of the hash chiplet, and that they must be exactly $8 \cdot d - 1$ rows apart, where $d$ is the depth of the node. It also ensures that the computation for the old node/root combination is immediately followed by the computation for the new node/root combination. + +The effect of this operation on the rest of the stack is: +* **No change** for positions starting from $4$. + +## FRIE2F4 +The `FRIE2F4` operation performs FRI layer folding by a factor of 4 for FRI protocol executed in a degree 2 extension of the base field. It also performs several computations needed for checking correctness of the folding from the previous layer as well as simplifying folding of the next FRI layer. + +The stack for the operation is expected to be arranged as follows: +- The first $8$ stack elements contain $4$ query points to be folded. Each point is represented by two field elements because points to be folded are in the extension field. We denote these points as $q_0 = (v_0, v_1)$, $q_1 = (v_2, v_3)$, $q_2 = (v_4, v_5)$, $q_3 = (v_6, v_7)$. +- The next element $f\_pos$ is the query position in the folded domain. It can be computed as $pos \mod n$, where $pos$ is the position in the source domain, and $n$ is size of the folded domain. +- The next element $d\_seg$ is a value indicating domain segment from which the position in the original domain was folded. It can be computed as $\lfloor \frac{pos}{n} \rfloor$. Since the size of the source domain is always $4$ times bigger than the size of the folded domain, possible domain segment values can be $0$, $1$, $2$, or $3$. +- The next element $poe$ is a power of initial domain generator which aids in a computation of the domain point $x$. +- The next two elements contain the result of the previous layer folding - a single element in the extension field denoted as $pe = (pe_0, pe_1)$. +- The next two elements specify a random verifier challenge $\alpha$ for the current layer defined as $\alpha = (a_0, a_1)$. +- The last element on the top of the stack ($cptr$) is expected to be a memory address of the layer currently being folded. + +The diagram below illustrates stack transition for `FRIE2F4` operation. + +![frie2f4](../../img/design/stack/crypto_ops/FRIE2F4.png) + +At the high-level, the operation does the following: +- Computes the domain value $x$ based on values of $poe$ and $d\_seg$. +- Using $x$ and $\alpha$, folds the query values $q_0, ..., q_3$ into a single value $r$. +- Compares the previously folded value $pe$ to the appropriate value of $q_0, ..., q_3$ to verify that the folding of the previous layer was done correctly. +- Computes the new value of $poe$ as $poe' = poe^4$ (this is done in two steps to keep the constraint degree low). +- Increments the layer address pointer by $8$. +- Shifts the stack by $1$ to the left. This moves an element from the stack overflow table into the last position on the stack top. + +To keep the degree of the constraints low, a number of intermediate values are used. Specifically, the operation relies on all $6$ helper registers, and also uses the first $10$ elements of the stack at the next state for degree reduction purposes. Thus, once the operation has been executed, the top $10$ elements of the stack can be considered to be "garbage". + +> TODO: add detailed constraint descriptions. See discussion [here](https://github.com/0xMiden/miden-vm/issues/567#issuecomment-1398088792). + +The effect on the rest of the stack is: +* **Left shift** starting from position $16$. + +## HORNERBASE +The `HORNERBASE` operation performs $8$ steps of the Horner method for evaluating a polynomial with coefficients over the base field at a point in the quadratic extension field. More precisely, it performs the following update to the accumulator on the stack + $$\mathsf{tmp} = (((\mathsf{acc} \cdot \alpha + a_7) \cdot \alpha + a_6) \cdot \alpha + a_5) \cdot \alpha + a_4$$ + + $$\mathsf{acc}^{'} = (((\mathsf{tmp} \cdot \alpha + a_3) \cdot \alpha + a_2) \cdot \alpha + a_1) \cdot \alpha + a_0$$ +where $a_i$ are the coefficients of the polynomial, $\alpha$ the evaluation point, $\mathsf{acc}$ the current accumulator value, $\mathsf{acc}^{'}$ the updated accumulator value, and $\mathsf{tmp}$ is a helper variable used for constraint degree reduction. + +The stack for the operation is expected to be arranged as follows: +- The first $8$ stack elements are the $8$ base field elements $a_0,\cdots , a_7$ representing the current 8-element batch of coefficients for the polynomial being evaluated. +- The next $5$ stack elements are irrelevant for the operation and unaffected by it. +- The next stack element contains the value of the memory pointer `alpha_ptr` to the evaluation point $\alpha$. The word address containing $\alpha = (\alpha_0, \alpha_1)$ is expected to have layout $[\alpha_0, \alpha_1, k_0, k_1]$ where $[k_0, k_1]$ is the second half of the memory word containing $\alpha$. Note that, in the context of the above expressions, we only care about the first half i.e., $[\alpha_0, \alpha_1]$, but providing the second half of the word in order to be able to do a one word memory read is more optimal than doing two element memory reads. +- The next $2$ stack elements contain the value of the current accumulator $\textsf{acc} = (\textsf{acc}_0, \textsf{acc}_1)$. + +The diagram below illustrates the stack transition for `HORNERBASE` operation. + +![horner_eval_base](../../img/design/stack/crypto_ops/HORNERBASE.png) + +After calling the operation: +- Helper registers $h_i$ will contain the values $[\alpha_0, \alpha_1, k_0, k_1, \mathsf{tmp}_0, \mathsf{tmp}_1]$. +- Stack elements $14$ and $15$ will contain the value of the updated accumulator i.e., $\mathsf{acc}^{'}$. + +More specifically, the stack transition for this operation must satisfy the following constraints: + +$$ +\begin{align*} + \mathsf{tmp}_0 &= \mathsf{acc}_0 \cdot \alpha_0^4 - 8 \cdot \mathsf{acc}_1 \cdot \alpha_0^3 \cdot \alpha_1 - 12 \cdot \mathsf{acc}_0 \cdot \alpha_0^2 \cdot \alpha_1^2 + &- 12 \cdot \mathsf{acc}_1 \cdot \alpha_0^2 \cdot \alpha_1^2 - 8 \cdot \mathsf{acc}_0 \cdot \alpha_0 \cdot \alpha_1^3 + &+ 8 \cdot \mathsf{acc}_1\cdot\alpha_0\cdot\alpha_1^3 + 2\cdot\mathsf{acc}_0\cdot\alpha_1^4 + &+ 6\cdot\mathsf{acc}_1\cdot\alpha_1^4 + s_7\cdot\alpha_0^3 - 6 \cdot s_7\cdot\alpha_0\cdot\alpha_1^2 + &- 2 \cdot s_7\cdot\alpha_1^3 + s_6\cdot\alpha_0^2 - 2\cdot s_6\cdot\alpha_1^2 + s_5\cdot\alpha_0 + s_4 \text{ | degree} = 5 +\end{align*} +$$ + +$$ +\begin{align*} +\mathsf{tmp}_1 &= \mathsf{acc}_1 \cdot \alpha_0^4 + 4 \cdot \mathsf{acc}_0 \cdot \alpha_0^3 \cdot \alpha_1 + 4 \cdot \mathsf{acc}_1 \cdot \alpha_0^3 \cdot \alpha_1 &+ 6 \cdot \mathsf{acc}_0 \cdot \alpha_0^2 \cdot \alpha_1^2 - 6 \cdot \mathsf{acc}_1 \cdot \alpha_0^2 \cdot \alpha_1^2 &- 4 \cdot \mathsf{acc}_0 \cdot \alpha_0\cdot\alpha_1^3 - 12 \cdot \mathsf{acc}_1 \cdot \alpha_0 \cdot \alpha_1^3 & - 3 \cdot\mathsf{acc}_0 \cdot \alpha_1^4 - \mathsf{acc}_1 \cdot \alpha_1^4 + 3 \cdot s_7 \cdot \alpha_0^2\cdot\alpha_1 &+ 3 \cdot s_7 \cdot \alpha_0\cdot\alpha_1^2 - s_7 \cdot\alpha_1^3 + 2\cdot s_6\cdot\alpha_0\cdot\alpha_1 + s_6\cdot\alpha_1^2 + s_5\cdot\alpha_1 \text{ | degree} = 5 +\end{align*} +$$ + +$$ +\begin{align*} +\mathsf{acc}_0^{'} &= \mathsf{tmp}_0\cdot\alpha_0^4 - 8 \cdot\mathsf{tmp}_1\cdot\alpha_0^3\cdot\alpha_1 - 12 \cdot\mathsf{tmp}_0\cdot\alpha_0^2\cdot\alpha_1^2 &- 12\cdot\mathsf{tmp}_1\cdot\alpha_0^2\cdot\alpha_1^2 - 8\cdot\mathsf{tmp}_0\cdot\alpha_0\cdot\alpha_1^3 + 8\cdot\mathsf{tmp}_1\cdot\alpha_0\cdot\alpha_1^3 &+ 2\cdot\mathsf{tmp}_0\cdot\alpha_1^4 + 6\cdot\mathsf{tmp}_1\cdot\alpha_1^4 + s_3\cdot\alpha_0^3 - 6\cdot s_3\cdot\alpha_0\cdot\alpha_1^2 &- 2\cdot s_3\cdot\alpha_1^3 + s_2\cdot\alpha_0^2 - 2\cdot s_2\cdot\alpha_1^2 + s_1\cdot\alpha_0 + s_0 \text{ | degree} = 5 +\end{align*} +$$ + +$$ +\begin{align*} +\mathsf{acc}_1^{'} &= \mathsf{tmp}_1\cdot\alpha_0^4 + 4\cdot\mathsf{tmp}_0\cdot\alpha_0^3\cdot\alpha_1 + 4\cdot\mathsf{tmp}_1\cdot\alpha_0^3\cdot\alpha_1 + 6\cdot\mathsf{tmp}_0\cdot\alpha_0^2\cdot\alpha_1^2 &- 6\cdot\mathsf{tmp}_1\cdot\alpha_0^2\cdot\alpha_1^2 - 4 \cdot \mathsf{tmp}_0\cdot\alpha_0\cdot\alpha_1^3 -12\cdot\mathsf{tmp}_1\cdot\alpha_0\cdot\alpha_1^3 &- 3\cdot\mathsf{tmp}_0\cdot\alpha_1^4 - \mathsf{tmp}_1\cdot\alpha_1^4 + 3\cdot s_3\cdot\alpha_0^2\cdot\alpha_1 &+ 3\cdot s_3\cdot\alpha_0\cdot\alpha_1^2 - s_3\cdot\alpha_1^3 + 2\cdot s_2\cdot\alpha_0\cdot\alpha_1 + s_2\cdot\alpha_1^2 + s_1\cdot\alpha_1 \text{ | degree} = 5 +\end{align*} +$$ + +The effect on the rest of the stack is: +* **No change.** + +The `HORNERBASE` makes one memory access request: + +$$ +u_{mem} = \alpha_0 + \alpha_1 \cdot op_{mem\_readword} + \alpha_2 \cdot ctx + \alpha_3 \cdot s_{13} + \alpha_4 \cdot clk + \alpha_{5} \cdot h_{0} + \alpha_{6} \cdot h_{1} + \alpha_{7} \cdot h_{3} + \alpha_{8} \cdot h_{4} +$$ + +## HORNEREXT +The `HORNEREXT` operation performs $4$ steps of the Horner method for evaluating a polynomial with coefficients over the quadratic extension field at a point in the quadratic extension field. More precisely, it performs the following update to the accumulator on the stack + $$\mathsf{tmp} = (\mathsf{acc} \cdot \alpha + a_3) \cdot \alpha + a_2$$ +$$\mathsf{acc}^{'} = (\mathsf{tmp} \cdot \alpha + a_1) \cdot \alpha + a_0$$ + +where $a_i$ are the coefficients of the polynomial, $\alpha$ the evaluation point, $\mathsf{acc}$ the current accumulator value, $\mathsf{acc}^{'}$ the updated accumulator value, and $\mathsf{tmp}$ is a helper variable used for constraint degree reduction. + +The stack for the operation is expected to be arranged as follows: +- The first $8$ stack elements contain $8$ base field elements that make up the current 4-element batch of coefficients, in the quadratic extension field, for the polynomial being evaluated. +- The next $5$ stack elements are irrelevant for the operation and unaffected by it. +- The next stack element contains the value of the memory pointer `alpha_ptr` to the evaluation point $\alpha$. The word address containing $\alpha = (\alpha_0, \alpha_1)$ is expected to have layout $[\alpha_0, \alpha_1, k_0, k_1]$ where $[k_0, k_1]$ is the second half of the memory word containing $\alpha$. Note that, in the context of the above expressions, we only care about the first half i.e., $[\alpha_0, \alpha_1]$, but providing the second half of the word in order to be able to do a one word memory read is more optimal than doing two element memory reads. +- The next $2$ stack elements contain the value of the current accumulator $\textsf{acc} = (\textsf{acc}_0, \textsf{acc}_1)$. + +The diagram below illustrates the stack transition for `HORNEREXT` operation. + +![horner_eval_ext](../../img/design/stack/crypto_ops/HORNEREXT.png) + +After calling the operation: +- Helper registers $h_i$ will contain the values $[\alpha_0, \alpha_1, k_0, k_1, \mathsf{tmp}_0, \mathsf{tmp}_1]$. +- Stack elements $14$ and $15$ will contain the value of the updated accumulator i.e., $\mathsf{acc}^{'}$. + +More specifically, the stack transition for this operation must satisfy the following constraints: + +$$ +\begin{align*} +\mathsf{tmp}_0 &= \mathsf{acc}_0\cdot \alpha_0^2 - 4\cdot \mathsf{acc}_1\cdot \alpha_0\cdot \alpha_1 - 2\cdot \mathsf{acc}_0\cdot \alpha_1^2 &- 2\cdot \mathsf{acc}_1\cdot \alpha_1^2 + s_6\cdot \alpha_0 -2\cdot s_7\cdot \alpha_1 + s_4 \text{ | degree} = 3 +\end{align*} +$$ + +$$ +\begin{align*} +\mathsf{tmp}_1 &= \mathsf{acc}_1\cdot \alpha_0^2 + 2\cdot \mathsf{acc}_0\cdot \alpha_0\cdot \alpha_1 + 2\cdot \mathsf{acc}_1\cdot \alpha_0\cdot \alpha_1 &+ \mathsf{acc}_0\cdot \alpha_1^2 - \mathsf{acc}_1\cdot \alpha_1^2 + s_7\cdot \alpha_0 + s_6\cdot \alpha_1 + s_7\cdot \alpha_1 + s_5 \text{ | degree} = 3 +\end{align*} +$$ + +$$ +\begin{align*} +\mathsf{acc}_0^{'} &= \mathsf{tmp}_0\cdot \alpha_0^2 - 4\cdot \mathsf{tmp}_1\cdot \alpha_0\cdot \alpha_1 - 2\cdot \mathsf{tmp}_0\cdot \alpha_1^2 & - 2\cdot \mathsf{tmp}_1\cdot \alpha_1^2 + s_2\cdot \alpha_0 - 2\cdot s_3\cdot \alpha_1 + s_0 \text{ | degree} = 3 +\end{align*} +$$ + +$$ +\begin{align*} +\mathsf{acc}_1^{'} &= \mathsf{tmp}_1\cdot \alpha_0^2 + 2\cdot \mathsf{tmp}_0\cdot \alpha_0\cdot \alpha_1 + 2\cdot \mathsf{tmp}_1\cdot \alpha_0\cdot \alpha_1 & + \mathsf{tmp}_0\cdot \alpha_1^2 - \mathsf{tmp}_1\cdot \alpha_1^2 + s_3\cdot \alpha_0 + s_2\cdot \alpha_1 + s_3\cdot \alpha_1 + s_1 \text{ | degree} = 3 +\end{align*} +$$ + +The effect on the rest of the stack is: +* **No change.** + +The `HORNEREXT` makes one memory access request: + +$$ +u_{mem} = \alpha_0 + \alpha_1 \cdot op_{mem\_readword} + \alpha_2 \cdot ctx + \alpha_3 \cdot s_{13} + \alpha_4 \cdot clk + \alpha_{5} \cdot h_{0} + \alpha_{6} \cdot h_{1} + \alpha_{7} \cdot h_{3} + \alpha_{8} \cdot h_{4} +$$ + +## EVALCIRCUIT + +The `EVALCIRCUIT` operation evaluates an arithmetic circuit, given its circuit description and a set of input values, using the [ACE](../chiplets/ace.md) chiplet and asserts that the evaluation is equal to zero. + +The stack is expected to be arranged as follows (from the top): +- A pointer to the circuit description with the [expected](../chiplets/ace.md#memory-layout) layout by the ACE chiplet. +- The number of quadratic extension field elements that are read during the `READ` [phase](../chiplets/ace.md#circuit-evaluation) of circuit evaluation. +- The number of base field elements representing the encodings of instructions that make up the circuit being evaluated during the `EVAL` [phase](../chiplets/ace.md#circuit-evaluation) of circuit evaluation. + +The diagram below illustrates this graphically. + +![evalcircuit](../../img/design/stack/crypto_ops/EVALCIRCUIT.png) + +Calling the operation has no effect on the stack or on helper registers. Instead, the operation makes a request to the `ACE` chiplet using the chiplets' bus. More precisely, let + +$$ +v_{ace} = \alpha_0 + \mathsf{ACE\_LABEL}\cdot\alpha_1 + ctx \cdot\alpha_2 + ptr\cdot\alpha_3 + clk\cdot\alpha_4 + n_{read}\cdot\alpha_5 + n_{eval}\cdot\alpha_6. +$$ + +where: +- $\mathsf{ACE\_LABEL}$ is the unique [operation labels](../chiplets/index.md#operation-labels) for initiating a circuit evaluation request to the ACE chiplet, +- $ctx$ is the memory context from which the operation was initiated, +- $clk$ is the clock cycle at which the operation was initiated, +- $ptr$, $n_{read}$ and $n_{eval}$ are as above. + +Then, using the above value, we can describe the constraint for the chiplets' bus column as follows: + +$$ +b_{chip}' \cdot v_{ace} = b_{chip} \text{ | degree} = 2 +$$ + +## LOG_PRECOMPILE + +The `log_precompile` operation logs a precompile event by recording two user-provided words (`TAG` and `COMM`) into the precompile transcript (implemented via an RPO sponge). Initialization and boundary enforcement are handled via variable‑length public inputs; see [Precompile flow](./precompiles.md) for a high‑level overview. This section concentrates on the stack interaction and bus messages. + +### Operation Overview + +The stack is expected to be arranged as `[COMM, TAG, PAD, ...]`. See [Precompiles](./precompiles.md#core-data) for a thorough explanation of the precompile commitment model. In brief: +- `TAG` encodes the precompile's event ID (first element) along with metadata or simple outputs (remaining elements), +- `COMM` commits to the precompile inputs (and may include outputs for long results), +- `PAD` is a word that will get overwritten in the next cycle. + +Additionally, the processor maintains a persistent precompile transcript state word `CAP` (the sponge capacity) that is updated with each `LOG_PRECOMPILE` invocation. This word is provided non-deterministically via helper registers and is denoted `CAP_PREV`. The virtual table bus links each removal to a matching insertion, ensuring a single, consistent state sequence. + +The operation evaluates `[CAP_NEXT, R0, R1] = RPO([CAP_PREV, TAG, COMM])`, with the following stack transition + +``` +Before: [COMM, TAG, PAD, ...] +After: [R1, R0, CAP_NEXT, ...] +``` + +The VM updates its internal precompile transcript state (`CAP_NEXT`) using a bus as we describe below. The rate outputs `R0` and `R1` are transient; together with `CAP_NEXT`, they are typically dropped by the caller immediately after logging. + +The operation uses the following helper registers: +- $h_0$: Hasher chiplet row address +- $h_1, h_2, h_3, h_4$: Previous capacity `CAP_PREV` + +Note: helper registers expose `CAP_PREV` for bus constraints only; the VM maintains the +transcript state internally between invocations. + +### Bus Communication + +#### Hasher chiplet + +The following two messages are sent to the hasher chiplet, ensuring the validity of the resulting permutation. Let $s_i$ denote the $i$-th stack column at that row (top of stack is $s_0$). The elements appearing on the bus are: + +$$ +\begin{aligned} +\mathsf{CAP}^{\text{prev}}_i &= h_{i+1} &&\text{(helper registers)}\\ +\mathsf{TAG}_i &= s_{7-i} &&\text{(stack slots 4..7)}\\ +\mathsf{COMM}_i &= s_{3-i} &&\text{(stack slots 0..3)} +\end{aligned} +\qquad i \in \{0,1,2,3\}. +$$ + +The input message therefore reduces the RPO state in the canonical order `[CAP_PREV, TAG, COMM]`: + +$$ +v_{\text{input}} = \alpha_0 + \alpha_1 \cdot op_{linhash} + \alpha_2 \cdot h_0 + \sum_{i=0}^{3} \alpha_{i+3} \cdot \mathsf{CAP}_{\text{prev},i} + \sum_{i=0}^{3} \alpha_{i+7} \cdot \mathsf{TAG}_i + \sum_{i=0}^{3} \alpha_{i+11} \cdot \mathsf{COMM}_i. +$$ + +Seven rows later, the `op_retstate` response provides the permuted state `[CAP_{next}, R0, R1]`. Denote the stack after the instruction by $s'_i$; the top twelve elements are `[R1, R0, CAP_NEXT]` in reverse order. Thus + +$$ +\begin{aligned} +\mathsf{R}_1{}_i &= s'_{3-i},\\ +\mathsf{R}_0{}_i &= s'_{7-i},\\ +\mathsf{CAP}^{\text{next}}_i &= s'_{11-i}, +\end{aligned} +\qquad i \in \{0,1,2,3\}, +$$ + +and the response message is + +$$ +v_{\text{output}} = \alpha_0 + \alpha_1 \cdot op_{retstate} + \alpha_2 \cdot (h_0 + 7) + \sum_{i=0}^{3} \alpha_{i+4} \cdot \mathsf{CAP}^{\text{next}}_i+ \sum_{i=0}^{3} \alpha_{i+8} \cdot \mathsf{R}_0{}_i + \sum_{i=0}^{3} \alpha_{i+12} \cdot \mathsf{R}_1{}_i. +$$ + +Using the above values, we can describe the constraint for the chiplet bus column as follows: + +$$ +b_{chip}' \cdot v_{input} \cdot v_{output} = b_{chip} +$$ + +The above constraint enforces that the specified input and output rows must be present in the trace of the hash chiplet, and that they must be exactly 7 rows apart. The RPO permutation outputs `[CAP, R0, R1]`; on the stack, the VM stores these words as `[R1, R0, CAP]`. + +Given the similarity with the `HPERM` opcode which sends the same message, albeit from different variables in the trace, it should be possible to combine the bus constraint in a way that avoids increasing the degree of the overall bus expression. + +### Capacity Initialization + +Inside the VM, the transcript state (sponge capacity) is tracked via the virtual table bus: each update removes the previous entry before inserting the next one. + +We denote the messages for removing and inserting the message as + +$$ +v_{rem} = \alpha_0 + \alpha_1 \cdot op_{log\_precompile} + \sum_{j=0}^{3} \alpha_{j+2} \cdot \mathsf{CAP\_PREV}_j +$$ + +$$ +v_{ins} = \alpha_0 + \alpha_1 \cdot op_{log\_precompile} + \sum_{j=0}^{3} \alpha_{j+2} \cdot \mathsf{CAP\_NEXT}_j +$$ + +The bus constraint is applied to the virtual table column as follows. + +$$ +b_{vtable}' \cdot v_{rem} = b_{vtable} \cdot v_{ins} +$$ + +To ensure the column accounts for the initial and final transcript state, the verifier initializes the bus with variable‑length public inputs (see kernel ROM chiplet). More specifically, it constrains the first value of the bus to be equal to + +$$ +b_{vtable,0} = \frac{v_{ins, init}}{v_{rem, last}} +$$ + +Usually, we initialize the transcript state to the empty word `[0,0,0,0]`, though it may also be used to extend an existing running state from a previous execution. The final transcript state is provided to the verifier (as a variable‑length public input) and enforced via the boundary constraint. +The messages $v_{ins, init}$ and $v_{rem, last}$ are given by + +$$ +v_{ins,init} = \alpha_0 + \alpha_1 \cdot op_{log\_precompile}, +$$ + +$$ +v_{rem,last} = \alpha_0 + \alpha_1 \cdot op_{log\_precompile} + \sum_{j=0}^{3} \alpha_{j+2} \cdot \mathsf{CAP\_FINAL}_j. +$$ + +The bus records only the transcript state (sponge capacity); the VM never finalizes the digest internally. Instead, the verifier (or any external consumer) reconstructs the transcript from the recorded requests. By convention, when a digest is required, the transcript is finalized by absorbing two empty words and applying one more permutation—matching the fact that `log_precompile` discards the rate outputs (`R0`, `R1`) after each absorption. diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/design/stack/field_ops.md b/versioned_docs/version-0.12 (stable)/miden-vm/design/stack/field_ops.md new file mode 100644 index 0000000..8e77166 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-vm/design/stack/field_ops.md @@ -0,0 +1,263 @@ +--- +title: "Field Operations" +sidebar_position: 4 +--- + +# Field Operations +In this section we describe the AIR constraints for Miden VM field operations (i.e., arithmetic operations over field elements). + +## ADD +Assume $a$ and $b$ are the elements at the top of the stack. The `ADD` operation computes $c \leftarrow (a + b)$. The diagram below illustrates this graphically. + +![add](../../img/design/stack/field_operations/ADD.png) + +Stack transition for this operation must satisfy the following constraints: + +$$ +s_0' - (s_0 + s_1) = 0 \text{ | degree} = 1 +$$ + +The effect on the rest of the stack is: +* **Left shift** starting from position $2$. + +## NEG +Assume $a$ is the element at the top of the stack. The `NEG` operation computes $b \leftarrow (-a)$. The diagram below illustrates this graphically. + +![neg](../../img/design/stack/field_operations/NEG.png) + +Stack transition for this operation must satisfy the following constraints: + +$$ +s_0' + s_0 = 0 \text{ | degree} = 1 +$$ + +The effect on the rest of the stack is: +* **No change** starting from position $1$. + +## MUL +Assume $a$ and $b$ are the elements at the top of the stack. The `MUL` operation computes $c \leftarrow (a \cdot b)$. The diagram below illustrates this graphically. + +![mul](../../img/design/stack/field_operations/MUL.png) + +Stack transition for this operation must satisfy the following constraints: + +$$ +s_0' - s_0 \cdot s_1 = 0 \text{ | degree} = 2 +$$ + +The effect on the rest of the stack is: +* **Left shift** starting from position $2$. + +## INV +Assume $a$ is the element at the top of the stack. The `INV` operation computes $b \leftarrow (a^{-1})$. The diagram below illustrates this graphically. + +![inv](../../img/design/stack/field_operations/INV.png) + +Stack transition for this operation must satisfy the following constraints: + +$$ +1 - s_0' \cdot s_0 = 0 \text{ | degree} = 2 +$$ + +Note that the above constraint can be satisfied only if the value in $s_0 \neq 0$. + +The effect on the rest of the stack is: +* **No change** starting from position $1$. + +## INCR +Assume $a$ is the element at the top of the stack. The `INCR` operation computes $b \leftarrow (a+1)$. The diagram below illustrates this graphically. + +![incr](../../img/design/stack/field_operations/INCR.png) + +Stack transition for this operation must satisfy the following constraints: + +$$ +s_0' - (s_0 + 1) = 0 \text{ | degree} = 1 +$$ + +The effect on the rest of the stack is: +* **No change** starting from position $1$. + +## NOT +Assume $a$ is a binary value at the top of the stack. The `NOT` operation computes $b \leftarrow (\lnot a)$. The diagram below illustrates this graphically. + +![not](../../img/design/stack/field_operations/NOT.png) + +Stack transition for this operation must satisfy the following constraints: + +$$ +s_0^2 - s_0 = 0 \text{ | degree} = 2 +$$ + +$$ +s_0' - (1 - s_0) = 0 \text{ | degree} = 1 +$$ + +The first constraint ensures that the value in $s_0$ is binary, and the second constraint ensures the correctness of the boolean `NOT` operation. + +The effect on the rest of the stack is: +* **No change** starting from position $1$. + +## AND +Assume $a$ and $b$ are binary values at the top of the stack. The `AND` operation computes $c \leftarrow (a \land b)$. The diagram below illustrates this graphically. + +![and](../../img/design/stack/field_operations/AND.png) + +Stack transition for this operation must satisfy the following constraints: + +$$ +s_i^2 - s_i = 0 \text{ for } i \in \{0, 1\} \text{ | degree} = 2 +$$ + +$$ +s_0' - s_0 \cdot s_1 = 0 \text{ | degree} = 2 +$$ + +The first two constraints ensure that the value in $s_0$ and $s_1$ are binary, and the third constraint ensures the correctness of the boolean `AND` operation. + +The effect on the rest of the stack is: +* **Left shift** starting from position $2$. + +## OR +Assume $a$ and $b$ are binary values at the top of the stack. The `OR` operation computes $c \leftarrow (a \lor b)$ The diagram below illustrates this graphically. + +![or](../../img/design/stack/field_operations/OR.png) + +Stack transition for this operation must satisfy the following constraints: + +$$ +s_i^2 - s_i = 0 \text{ for } i \in \{0, 1\} \text{ | degree} = 2 +$$ + +$$ +s_{0}' - (s_{1} + s_{0} - s_{1} \cdot s_{0}) = 0 \text{ | degree} = 2 +$$ + +The first two constraints ensure that the value in $s_0$ and $s_1$ are binary, and the third constraint ensures the correctness of the boolean `OR` operation. + +The effect on the rest of the stack is: +* **Left shift** starting from position $2$. + +## EQ +Assume $a$ and $b$ are the elements at the top of the stack. The `EQ` operation computes $c$ such that $c = 1$ if $a = b$, and $0$ otherwise. The diagram below illustrates this graphically. + +![eq](../../img/design/stack/field_operations/EQ.png) + +Stack transition for this operation must satisfy the following constraints: + +$$ +s_0' \cdot (s_0 - s_1) = 0 \text{ | degree} = 2 +$$ + +$$ +s_0' - (1 - (s_0 - s_1) \cdot h_0) = 0 \text{ | degree} = 2 +$$ + +To satisfy the above constraints, the prover must populate the value of helper register $h_0$ as follows: +* If $s_0 \neq s_1$, set $h_0 = \frac{1}{s_0 - s_1}$. +* Otherwise, set $h_0$ to any value (e.g., $0$). + +The effect on the rest of the stack is: +* **Left shift** starting from position $2$. + +## EQZ +Assume $a$ is the element at the top of the stack. The `EQZ` operation computes $b$ such that $b = 1$ if $a = 0$, and $0$ otherwise. The diagram below illustrates this graphically. + +![eqz](../../img/design/stack/field_operations/EQZ.png) + +Stack transition for this operation must satisfy the following constraints: + +$$ +s_0' \cdot s_0 = 0 \text{ | degree} = 2 +$$ + +$$ +s_0' - (1 - s_0 \cdot h_0) = 0 \text{ | degree} = 2 +$$ + +To satisfy the above constraints, the prover must populate the value of helper register $h_0$ as follows: +* If $s_0 \neq 0$, set $h_0 = \frac{1}{s_0}$. +* Otherwise, set $h_0$ to any value (e.g., $0$). + +The effect on the rest of the stack is: +* **No change** starting from position $1$. + +## EXPACC +The `EXPACC` operation computes one round of the expression $base^{exp}$. It is expected that `Expacc` is called at least `num_exp_bits` times, where `num_exp_bits` is the number of bits required to represent `exp`. + +It pops $4$ elements from the top of the stack, performs a single round of exponent aggregation, and pushes the resulting $4$ values onto the stack. The diagram below illustrates this graphically. + +![expacc](../../img/design/stack/field_operations/EXPACC.png) + +Expacc is based on the observation that the exponentiation of a number can be computed by repeatedly squaring the base and multiplying those powers of the base by the accumulator, for the powers of the base which correspond to the exponent's bits which are set to 1. + +For example, take $b^5 = (b^2)^2 \cdot b$. Over the course of 3 iterations ($5$ is $101$ in binary), the algorithm will compute $b$, $b^2$ and $b^4$ (placed in `base_acc`). Hence, we want to multiply `base_acc` in `acc` when $base_acc = b$ and when $base_acc = b^4$, which occurs on the first and third iterations (corresponding to the $1$ bits in the binary representation of 5). + +Stack transition for this operation must satisfy the following constraints: + +`bit'` should be a binary. + +$$ +s_0'^{2} - s_0' = 0 \text{ | degree} = 2 +$$ + +The `base` in the next frame should be the square of the `base` in the current frame. + +$$ +s_1' - s_1^{2} = 0 \text{ | degree} = 2 +$$ + +The value `val` in the helper register is computed correctly using the `bit` and `exp` in next and current frame respectively. + +$$ +h_0 - ((s_1 - 1) * s_0' + 1) = 0 \text{ | degree} = 2 +$$ + +The `acc` in the next frame is the product of `val` and `acc` in the current frame. + +$$ +s_2' - s_2 * h_0 = 0 \text{ | degree} = 2 +$$ + +`exp` in the next frame is half of `exp` in the current frame (accounting for even/odd). + +$$ +s_3' - (s_3 * 2 + s_0') = 0 \text{ | degree} = 1 +$$ + +The effect on the rest of the stack is: +* **No change** starting from position $4$. + +## EXT2MUL +The `EXT2MUL` operation pops top $4$ values from the top of the stack, performs multiplication between the two extension field elements, and pushes the resulting $4$ values onto the stack. The diagram below illustrates this graphically. + +![ext2mul](../../img/design/stack/field_operations/EXT2MUL.png) + +Stack transition for this operation must satisfy the following constraints: + +The first stack element should be unchanged in the next frame. + +$$ +s_0' - s_0 = 0 \text{ | degree} = 1 +$$ + +The second stack element should be unchanged in the next frame. + +$$ +s_1' - s_1 = 0 \text{ | degree} = 1 +$$ + +The third stack element should satisfy the following constraint. + +$$ +s_2' - (s_0 + s_1) \cdot (s_2 + s_3) + s_0 \cdot s_2 = 0 \text{ | degree} = 2 +$$ + +The fourth stack element should satisfy the following constraint. + +$$ +s_3' - s_1 \cdot s_3 + 2 \cdot s_0 \cdot s_2 = 0 \text{ | degree} = 2 +$$ + +The effect on the rest of the stack is: +* **No change** starting from position $4$. diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/design/stack/index.md b/versioned_docs/version-0.12 (stable)/miden-vm/design/stack/index.md new file mode 100644 index 0000000..a61edf7 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-vm/design/stack/index.md @@ -0,0 +1,223 @@ +--- +title: "Operand Stack" +sidebar_position: 1 +--- + +# Operand stack + +Miden VM is a stack machine. The stack is a push-down stack of practically unlimited depth (in practical terms, the depth will never exceed $2^{32}$), but only the top $16$ items are directly accessible to the VM. Items on the stack are elements in a prime field with modulus $2^{64} - 2^{32} + 1$. + +To keep the constraint system for the stack manageable, we impose the following rules: + +1. All operations executed on the VM can shift the stack by at most one item. That is, the end result of an operation must be that the stack shrinks by one item, grows by one item, or the number of items on the stack stays the same. +2. Stack depth must always be greater than or equal to $16$. At the start of program execution, the stack is initialized with exactly $16$ input values, all of which could be $0$'s. +3. By the end of program execution, exactly $16$ items must remain on the stack (again, all of them could be $0$'s). These items comprise the output of the program. + +To ensure that managing stack depth does not impose significant burden, we adopt the following rule: + +* When the stack depth is $16$, removing additional items from the stack does not change its depth. To keep the depth at $16$, $0$'s are inserted into the deep end of the stack for each removed item. + +## Stack representation + +The VM allocates $19$ trace columns for the stack. The layout of the columns is illustrated below. + +![trace_layout](../../img/design/stack/trace_layout.png) + +The meaning of the above columns is as follows: + +* $s_0 ... s_{15}$ are the columns representing the top $16$ slots of the stack. +* Column $b_0$ contains the number of items on the stack (i.e., the stack depth). In the above picture, there are 16 items on the stacks, so $b_0 = 16$. +* Column $b_1$ contains an address of a row in the "overflow table" in which we'll store the data that doesn't fit into the top $16$ slots. When $b_1 = 0$, it means that all stack data fits into the top $16$ slots of the stack. +* Helper column $h_0$ is used to ensure that stack depth does not drop below $16$. Values in this column are set by the prover non-deterministically to $\frac{1}{b_0 - 16}$ when $b_0 \neq 16$, and to any other value otherwise. + +### Overflow table + +To keep track of the data which doesn't fit into the top $16$ stack slots, we'll use an overflow table. This will be a [virtual table](../lookups/multiset.md#virtual-tables). To represent this table, we'll use a single auxiliary column $p_1$. + +The table itself can be thought of as having 3 columns as illustrated below. + +![overflow_table_layout](../../img/design/stack/overflow_table_layout.png) + +The meaning of the columns is as follows: + +* Column $t_0$ contains row address. Every address in the table must be unique. +* Column $t_1$ contains the value that overflowed the stack. +* Column $t_2$ contains the address of the row containing the value that overflowed the stack right before the value in the current row. For example, in the picture above, first value $a$ overflowed the stack, then $b$ overflowed the stack, and then value $c$ overflowed the stack. Thus, row with value $b$ points back to the row with value $a$, and row with value $c$ points back to the row with value $b$. + +To reduce a table row to a single value, we'll compute a randomized product of column values as follows: + +$$ +r_i = \alpha_0 + \alpha_1 \cdot t_{0, i} + \alpha_2 \cdot t_{1, i} + \alpha_3 \cdot t_{2, i} +$$ + +Then, when row $i$ is added to the table, we'll update the value in the $p_1$ column like so: + +$$ +p_1' = p_1 \cdot r_i +$$ + +Analogously, when row $i$ is removed from the table, we'll update the value in column $p_1$ like so: + +$$ +p_1' = \frac{p_1}{r_i} +$$ + +The initial value of $p_1$ is set to $1$. Thus, if by the time Miden VM finishes executing a program the table is empty (we added and then removed exactly the same set of rows), $p_1$ will also be equal to $1$. + +There are a couple of other rules we'll need to enforce: + +* We can delete a row only after the row has been inserted into the table. +* We can't insert a row with the same address twice into the table (even if the row was inserted and then deleted). + +How these are enforced will be described a bit later. + +## Right shift + +If an operation adds data to the stack, we say that the operation caused a right shift. For example, `PUSH` and `DUP` operations cause a right shift. Graphically, this looks like so: + +![stack_right_shift](../../img/design/stack/stack_right_shift.png) + +Here, we pushed value $v_{17}$ onto the stack. All other values on the stack are shifted by one slot to the right and the stack depth increases by $1$. There is not enough space at the top of the stack for all $17$ values, thus, $v_1$ needs to be moved to the overflow table. + +To do this, we need to rely on another column: $k_0$. This is a system column which keeps track of the current VM cycle. The value in this column is simply incremented by $1$ with every step. + +The row we want to add to the overflow table is defined by tuple $(clk, v1, 0)$, and after it is added, the table would look like so: + +![stack_overflow_table_post_1_right_shift](../../img/design/stack/stack_overflow_table_post_1_right_shift.png) + +The reason we use VM clock cycle as row address is that the clock cycle is guaranteed to be unique, and thus, the same row can not be added to the table twice. + +Let's push another item onto the stack: + +![stack_overflow_push_2nd_item](../../img/design/stack/stack_overflow_push_2nd_item.png) + +Again, as we push $v_{18}$ onto the stack, all items on the stack are shifted to the right, and now $v_2$ needs to be moved to the overflow table. The tuple we want to insert into the table now is $(clk+1, v2, clk)$. After the operation, the overflow table will look like so: + +![stack_overflow_table_post_2_right_shift](../../img/design/stack/stack_overflow_table_post_2_right_shift.png) + +Notice that $t_2$ for row which contains value $v_2$ points back to the row with address $clk$. + +Overall, during a right shift we do the following: + +* Increment stack depth by $1$. +* Shift stack columns $s_0, ..., s_{14}$ right by $1$ slot. +* Add a row to the overflow table described by tuple $(k_0, s_{15}, b_0)$. +* Set the next value of $b_1$ to the current value of $k_0$. + +Also, as mentioned previously, the prover sets values in $h_0$ non-deterministically to $\frac{1}{b_0 - 16}$. + +## Left shift + +If an operation removes an item from the stack, we say that the operation caused a left shift. For example, a `DROP` operation causes a left shift. Assuming the stack is in the state we left it at the end of the previous section, graphically, this looks like so: + +![stack_1st_left_shift](../../img/design/stack/stack_1st_left_shift.png) + +Overall, during the left shift we do the following: + +* When stack depth is greater than $16$: + * Decrement stack depth by $1$. + * Shift stack columns $s_1, ..., s_{15}$ left by $1$ slot. + * Remove a row from the overflow table with $t_0$ equal to the current value of $b_1$. + * Set the next value of $s_{15}$ to the value in $t_1$ of the removed overflow table row. + * Set the next value of $b_1$ to the value in $t_2$ of the removed overflow table row. +* When the stack depth is equal to $16$: + * Keep the stack depth the same. + * Shift stack columns $s_1, ..., s_{15}$ left by $1$ slot. + * Set the value of $s_{15}$ to $0$. + * Set the value to $h_0$ to $0$ (or any other value). + +If the stack depth becomes (or remains) $16$, the prover can set $h_0$ to any value (e.g., $0$). But if the depth is greater than $16$ the prover sets $h_0$ to $\frac{1}{b_0 - 16}$. + +## AIR Constraints + +To simplify constraint descriptions, we'll assume that the VM exposes two binary flag values described below. + +| Flag | Degree | Description | +| --------- | ------ | ------------------------------------------------------------------------------------------------ | +| $f_{shr}$ | 6 | When this flag is set to $1$, the instruction executing on the VM is performing a "right shift". | +| $f_{shl}$ | 5 | When this flag is set to $1$, the instruction executing on the VM is performing a "left shift". | + +These flags are mutually exclusive. That is, if $f_{shl}=1$, then $f_{shr}=0$ and vice versa. However, both flags can be set to $0$ simultaneously. This happens when the executed instruction does not shift the stack. How these flags are computed is described [here](./op_constraints.md). + +### Stack overflow flag + +Additionally, we'll define a flag to indicate whether the overflow table contains values. This flag will be set to $0$ when the overflow table is empty, and to $1$ otherwise (i.e., when stack depth $>16$). This flag can be computed as follows: + +$$ +f_{ov} = (b_0 - 16) \cdot h_0 \text{ | degree} = 2 +$$ + +To ensure that this flag is set correctly, we need to impose the following constraint: + +$$ +(1 - f_{ov}) \cdot (b_0 - 16) = 0 \text{ | degree} = 3 +$$ + +The above constraint can be satisfied only when either of the following holds: + +* $b_0 = 16$, in which case $f_{ov}$ evaluates to $0$, regardless of the value of $h_0$. +* $f_{ov} = 1$, in which case $b_0$ cannot be equal to $16$ (and $h_0$ must be set to $\frac{1}{b_0 - 16}$). + +### Stack depth constraints +To make sure stack depth column $b_0$ is updated correctly, we need to impose the following constraints: + +| Condition | Constraint__ | Description | +| --------------------------- | ---------------- | -------------------------------------------------------------------------------------------------------------------- | +| $f_{shr}=1$ | $b'_0 = b_0 + 1$ | When the stack is shifted to the right, stack depth should be incremented by $1$. | +| $f_{shl}=1$
$f_{ov}=1$ | $b'_0 = b_0 - 1$ | When the stack is shifted to the left and the overflow table is not empty, stack depth should be decremented by $1$. | +| otherwise | $b'_0 = b_0$ | In all other cases, stack depth should not change. | + +We can combine the above constraints into a single expression as follows: + +$$ +b'_0 - b_0 + f_{shl} \cdot f_{ov} - f_{shr} = 0 \text{ | degree} = 7 +$$ + +### Overflow table constraints + +When the stack is shifted to the right, a tuple $(k_0, s_{15}, b_1)$ should be added to the overflow table. We will denote value of the row to be added to the table as follows: + +$$ +v = \alpha_0 + \alpha_1 \cdot k_0 + \alpha_2 \cdot s_{15} + \alpha_3 \cdot b_1 +$$ + +When the stack is shifted to the left, a tuple $(b_1, s'_{15}, b'_1)$ should be removed from the overflow table. We will denote value of the row to be removed from the table as follows. + +$$ +u = \alpha_0 + \alpha_1 \cdot b_1 + \alpha_2 \cdot s'_{15} + \alpha_3 \cdot b'_1 +$$ + +Using the above variables, we can ensure that right and left shifts update the overflow table correctly by enforcing the following constraint: + +$$ +p_1' \cdot (u \cdot f_{shl} \cdot f_{ov} + 1 - f_{shl} \cdot f_{ov}) = p_1 \cdot (v \cdot f_{shr} + 1 - f_{shr}) \text{ | degree} = 9 +$$ + +The above constraint reduces to the following under various flag conditions: + +| Condition | Applied constraint | +| -------------------------------------------------- | -------------------- | +| $f_{shl}=1$, $f_{shr}=0$, $f_{ov}=0$ | $p_1' = p_1$ | +| $f_{shl}=1$, $f_{shr}=0$, $f_{ov}=1$ | $p_1' \cdot u = p_1$ | +| $f_{shl}=0$, $f_{shr}=1$, $f_{ov}=1 \text{ or } 0$ | $p_1' = p_1 \cdot v$ | +| $f_{shl}=0$, $f_{shr}=0$, $f_{ov}=1 \text{ or } 0$ | $p_1' = p_1$ | + +Notice that in the case of the left shift, the constraint forces the prover to set the next values of $s_{15}$ and $b_1$ to values $t_1$ and $t_2$ of the row removed from the overflow table. + +In case of a right shift, we also need to make sure that the next value of $b_1$ is set to the current value of $k_0$. This can be done with the following constraint: + +$$ +f_{shr} \cdot (b'_1 - k_0) = 0 \text{ | degree} = 7 +$$ + +In case of a left shift, when the overflow table is empty, we need to make sure that a $0$ is "shifted in" from the right (i.e., $s_{15}$ is set to $0$). This can be done with the following constraint: + +$$ +f_{shl} \cdot (1 - f_{ov}) \cdot s_{15}' = 0 \text{ | degree} = 8 +$$ + +### Boundary constraints +In addition to the constraints described above, we also need to enforce the following boundary constraints: +* $b_0 = 16$ at the first and at the last row of execution trace. +* $b_1 = 0$ at the first and at the last row of execution trace. +* $p_1 = 1$ at the first and at the last row of execution trace. diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/design/stack/io_ops.md b/versioned_docs/version-0.12 (stable)/miden-vm/design/stack/io_ops.md new file mode 100644 index 0000000..dda8533 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-vm/design/stack/io_ops.md @@ -0,0 +1,202 @@ +--- +title: "Input / Output Operations" +sidebar_position: 7 +--- + +# Input / output operations +In this section we describe the AIR constraints for Miden VM input / output operations. These operations move values between the stack and other components of the VM such as program code (i.e., decoder), memory, and advice provider. + +### PUSH +The `PUSH` operation pushes the provided immediate value onto the stack non-deterministically (i.e., sets the value of $s_0$ register); it is the responsibility of the [Op Group Table](../decoder/index.md#op-group-table) to ensure that the correct value was pushed on the stack. The semantics of this operation are explained in the [decoder section](../decoder/index.md#handling-immediate-values). + +The effect of this operation on the rest of the stack is: +* **Right shift** starting from position $0$. + +### SDEPTH +Assume $a$ is the current depth of the stack stored in the stack bookkeeping register $b_0$ (as described [here](./index.md#stack-representation)). The `SDEPTH` pushes $a$ onto the stack. The diagram below illustrates this graphically. + +![sdepth](../../img/design/stack/io_ops/SDEPTH.png) + +Stack transition for this operation must satisfy the following constraints: + +$$ +s_0' - b_0 = 0 \text{ | degree} = 1 +$$ + +The effect of this operation on the rest of the stack is: +* **Right shift** starting from position $0$. + +### ADVPOP +Assume $a$ is an element at the top of the advice stack. The `ADVPOP` operation removes $a$ from the advice stack and pushes it onto the operand stack. The diagram below illustrates this graphically. + +![advpop](../../img/design/stack/io_ops/ADVPOP.png) + +The `ADVPOP` operation does not impose any constraints against the first element of the operand stack. + +The effect of this operation on the rest of the operand stack is: +* **Right shift** starting from position $0$. + +### ADVPOPW +Assume $a$, $b$, $c$, and $d$, are the elements at the top of the advice stack (with $a$ being on top). The `ADVPOPW` operation removes these elements from the advice stack and puts them onto the operand stack by overwriting the top $4$ stack elements. The diagram below illustrates this graphically. + +![advpopw](../../img/design/stack/io_ops/ADVPOPW.png) + +The `ADVPOPW` operation does not impose any constraints against the top $4$ elements of the operand stack. + +The effect of this operation on the rest of the operand stack is: +* **No change** starting from position $4$. + +## Memory access operations +Miden VM exposes several operations for reading from and writing to random access memory. Memory in Miden VM is managed by the [Memory chiplet](../chiplets/memory.md). + +Communication between the stack and the memory chiplet is accomplished via the chiplet bus $b_{chip}$. To make requests to the chiplet bus we need to divide its current value by the value representing memory access request. The structure of memory access request value is described [here](../chiplets/memory.md#memory-row-value). + +To enforce the correctness of memory access, we can use the following constraint: + +$$ +b_{chip}' \cdot u_{mem} = b_{chip} \text{ | degree} = 2 +$$ + +In the above, $u_{mem}$ is the value of memory access request. Thus, to describe AIR constraint for memory operations, it is sufficient to describe how $u_{mem}$ is computed. We do this in the following sections. + +### MLOADW +Assume that the word with elements $v_0, v_1, v_2, v_3$ is located in memory starting at address $a$. The `MLOADW` operation pops an element off the stack, interprets it as a memory address, and replaces the remaining 4 elements at the top of the stack with values located at the specified address. The diagram below illustrates this graphically. + +![mloadw](../../img/design/stack/io_ops/MLOADW.png) + +To simplify description of the memory access request value, we first define a variable for the value that represents the state of memory after the operation: + +$$ +v = \sum_{i=0}^3\alpha_{i+5} \cdot s_{3-i}' +$$ + +Using the above variable, we define the value representing the memory access request as follows: + +$$ +u_{mem} = \alpha_0 + \alpha_1 \cdot op_{mem\_readword} + \alpha_2 \cdot ctx + \alpha_3 \cdot s_0 + \alpha_4 \cdot clk + v +$$ + +In the above: +- $op_{mem\_readword}$ is the unique [operation label](../chiplets/index.md#operation-labels) of the memory "read word" operation. +- $ctx$ is the identifier of the current memory context. +- $s_0$ is the memory address from which the values are to be loaded onto the stack. +- $clk$ is the current clock cycle of the VM. + +The effect of this operation on the rest of the stack is: +* **Left shift** starting from position $5$. + +### MLOAD +Assume that the element $v$ is located in memory at address $a$. The `MLOAD` operation pops an element off the stack, interprets it as a memory address, and pushes the element located at the specified address to the stack. The diagram below illustrates this graphically. + +![mload](../../img/design/stack/io_ops/MLOAD.png) + + +We define the value representing the memory access request as follows: + +$$ +u_{mem} = \alpha_0 + \alpha_1 \cdot op_{mem\_readelement} + \alpha_2 \cdot ctx + \alpha_3 \cdot s_0 + \alpha_4 \cdot clk + \alpha_5 \cdot v +$$ + +In the above: +- $op_{mem\_readelement}$ is the unique [operation label](../chiplets/index.md#operation-labels) of the memory "read element" operation. +- $ctx$ is the identifier of the current memory context. +- $s_0$ is the memory address from which the value is to be loaded onto the stack. +- $clk$ is the current clock cycle of the VM. + +The effect of this operation on the rest of the stack is: +* **No change** starting from position $1$. + +### MSTOREW +The `MSTOREW` operation pops an element off the stack, interprets it as a memory address, and writes the remaining $4$ elements at the top of the stack into memory starting at the specified address. The stored elements are not removed from the stack. The diagram below illustrates this graphically. + +![mstorew](../../img/design/stack/io_ops/MSTOREW.png) + +After the operation the contents of memory at addresses $a$, $a+1$, $a+2$, $a+3$ would be set to $v_0, v_1, v_2, v_3$, respectively. + +To simplify description of the memory access request value, we first define a variable for the value that represents the state of memory after the operation: + +$$ +v = \sum_{i=0}^3\alpha_{i+5} \cdot s_{3-i}' +$$ + +Using the above variable, we define the value representing the memory access request as follows: + +$$ +u_{mem} = \alpha_0 + \alpha_1 \cdot op_{mem\_writeword} + \alpha_2 \cdot ctx + \alpha_3 \cdot s_0 + \alpha_4 \cdot clk + v +$$ + +In the above: +- $op_{mem\_writeword}$ is the unique [operation label](../chiplets/index.md#operation-labels) of the memory "write word" operation. +- $ctx$ is the identifier of the current memory context. +- $s_0$ is the memory address into which the values from the stack are to be saved. +- $clk$ is the current clock cycle of the VM. + +The effect of this operation on the rest of the stack is: +* **Left shift** starting from position $1$. + +### MSTORE +The `MSTORE` operation pops an element off the stack, interprets it as a memory address, and writes the remaining element at the top of the stack into memory at the specified memory address. The diagram below illustrates this graphically. + +![mstore](../../img/design/stack/io_ops/MSTORE.png) + +After the operation the contents of memory at address $a$ would be set to $b$. + +We define the value representing the memory access request as follows: + +$$ +u_{mem} = \alpha_0 + \alpha_1 \cdot op_{mem\_writeelement} + \alpha_2 \cdot ctx + \alpha_3 \cdot s_0 + \alpha_4 \cdot clk + \alpha_5 \cdot v +$$ + +In the above: +- $op_{mem\_writeelement} $ is the unique [operation label](../chiplets/index.md#operation-labels) of the memory "write element" operation. +- $ctx$ is the identifier of the current memory context. +- $s_0$ is the memory address into which the value from the stack is to be saved. +- $clk$ is the current clock cycle of the VM. + +The effect of this operation on the rest of the stack is: +* **Left shift** starting from position $1$. + +### MSTREAM + +The `MSTREAM` operation loads two words from memory, and replaces the top 8 elements of the stack with them, element-wise, in stack order. The start memory address from which the words are loaded is stored in the 13th stack element (position 12). The diagram below illustrates this graphically. + +![mstream](../../img/design/stack/io_ops/MSTREAM.png) + +After the operation, the memory address is incremented by 8. + +$$ +s_{12}' = s_{12} + 8 +$$ + +To simplify description of the memory access request value, we first define variables for the values that represent the state of memory after the operation: + +$$ +v_1 = \sum_{i=0}^3\alpha_{i+5} \cdot s_{7-i}' +$$ + +$$ +v_2 = \sum_{i=0}^3\alpha_{i+5} \cdot s_{3-i}' +$$ + +Using the above variables, we define the values representing the memory access request as follows: + +$$ +u_{mem, 1} = \alpha_0 + \alpha_1 \cdot op_{mem\_readword} + \alpha_2 \cdot ctx + \alpha_3 \cdot s_{12} + \alpha_4 \cdot clk + v_1 +$$ + +$$ +u_{mem, 2} = \alpha_0 + \alpha_1 \cdot op_{mem\_readword} + \alpha_2 \cdot ctx + \alpha_3 \cdot (s_{12} + 4) + \alpha_4 \cdot clk + v_2 +$$ + +$$ +u_{mem} = u_{mem, 1} \cdot u_{mem, 2} +$$ + +In the above: +- $op_{mem\_readword}$ is the unique [operation label](../chiplets/index.md#operation-labels) of the memory "read word" operation. +- $ctx$ is the identifier of the current memory context. +- $s_{12}$ and $s_{12} + 4$ are the memory addresses from which the words are to be loaded onto the stack. +- $clk$ is the current clock cycle of the VM. + +The effect of this operation on the rest of the stack is: +* **No change** starting from position $8$ except position $12$. diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/design/stack/op_constraints.md b/versioned_docs/version-0.12 (stable)/miden-vm/design/stack/op_constraints.md new file mode 100644 index 0000000..f10466e --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-vm/design/stack/op_constraints.md @@ -0,0 +1,311 @@ +--- +title: "Stack Operation Constraints" +sidebar_position: 2 +--- + +# Stack operation constraints + +In addition to the constraints described in the previous section, we need to impose constraints to check that each VM operation is executed correctly. + +For this purpose the VM exposes a set of operation-specific flags. These flags are set to $1$ when a given operation is executed, and to $0$ otherwise. The naming convention for these flags is $f_{opname}$. For example, $f_{dup}$ would be set to $1$ when `DUP` operation is executed, and to $0$ otherwise. Operation flags are discussed in detail in the section [below](#operation-flags). + +To describe how operation-specific constraints work, let's use an example with `DUP` operation. This operation pushes a copy of the top stack item onto the stack. The constraints we need to impose for this operation are as follows: + +$$ +f_{dup} \cdot (s'_0 - s_0) = 0 +f_{dup} \cdot (s'_{i+1} - s_i) = 0 \ \text{ for } i \in [0, 15) +$$ + +The first constraint enforces that the top stack item in the next row is the same as the top stack item in the current row. The second constraint enforces that all stack items (starting from item $0$) are shifted to the right by $1$. We also need to impose all the constraints discussed in the previous section, be we omit them here. + +Let's write similar constraints for `DUP1` operation, which pushes a copy of the second stack item onto the stack: + +$$ +f_{dup1} \cdot (s'_0 - s_1) = 0 +f_{dup1} \cdot (s'_{i+1} - s_i) = 0 \ \text{ for } i \in [0, 15) +$$ + +It is easy to notice that while the first constraint changed, the second constraint remained the same - i.e., we are still just shifting the stack to the right. + +In fact, for most operations it makes sense to make a distinction between constraints unique to the operation vs. more general constraints which enforce correct behavior for the stack items not affected by the operation. In the subsequent sections we describe in detail only the former constraints, and provide high-level descriptions of the more general constraints. Specifically, we indicate how the operation affects the rest of the stack (e.g., shifts right starting from position $0$). + +## Operation flags +As mentioned above, operation flags are used as selectors to enforce operation-specific constraints. That is, they turn on relevant constraints for a given operation. In total, the VM provides $88$ unique operations, and thus, there are $88$ operation flags (not all of them currently used). + +Operation flags are mutually exclusive. That is, if one flag is set to $1$, all other flags are set to $0$. Also, one of the flags is always guaranteed to be set to $1$. + +To compute values of operation flags we use _op bits_ registers located in the [decoder](../decoder/index.md#decoder-trace). These registers contain binary representations of operation codes (opcodes). Each opcode consists of $7$ bits, and thus, there are $7$ _op bits_ registers. We denote these registers as $b_0, ..., b_6$. The values are computed by multiplying the op bit registers in various combinations. Notice that binary encoding down below is showed in big-endian order, so the flag bits correspond to the reverse order of the _op bits_ registers, from $b_6$ to $b_0$. + +For example, the value of the flag for `NOOP`, which is encoded as `0000000`, is computed as follows: + +$$ +f_{noop} = (1 - b_6) \cdot (1 - b_5) \cdot (1 - b_4) \cdot (1 - b_3) \cdot (1 - b_2) \cdot (1 - b_1) \cdot (1 - b_0) +$$ + +While the value of the `DROP` operation, which is encoded as `0101001` is computed as follows: + +$$ +f_{drop} = (1 - b_6) \cdot b_5 \cdot (1 - b_4) \cdot b_3 \cdot (1 - b_2) \cdot (1 - b_1) \cdot b_0 +$$ + +As can be seen from above, the degree for both of these flags is $7$. Since degree of constraints in Miden VM can go up to $9$, this means that operation-specific constraints cannot exceed degree $2$. However, there are some operations which require constraints of higher degree (e.g., $3$ or even $5$). To support such constraints, we adopt the following scheme. + +We organize the operations into $4$ groups as shown below and also introduce two extra registers $e_0$ and $e_1$ for degree reduction: + +| $b_6$ | $b_5$ | $b_4$ | $b_3$ | $b_2$ | $b_1$ | $b_0$ | $e_0$ | $e_1$ | # of ops | degree | +| :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :------: | :----: | +| 0 | x | x | x | x | x | x | 0 | 0 | 64 | 7 | +| 1 | 0 | 0 | x | x | x | - | 0 | 0 | 8 | 6 | +| 1 | 0 | 1 | x | x | x | x | 1 | 0 | 16 | 5 | +| 1 | 1 | x | x | x | - | - | 0 | 1 | 8 | 4 | + +In the above: +* Operation flags for operations in the first group (with prefix `0`), are computed using all $7$ op bits, and thus their degree is $7$. +* Operation flags for operations in the second group (with prefix `100`), are computed using only the first $6$ op bits, and thus their degree is $6$. +* Operation flags for operations in the third group (with prefix `101`), are computed using all $7$ op bits. We use the extra register $e_0$ (which is set to $b_6 \cdot (1-b_5) \cdot b_4$) to reduce the degree by $2$. Thus, the degree of op flags in this group is $5$. +* Operation flags for operations in the fourth group (with prefix `11`), are computed using only the first $5$ op bits. We use the extra register $e_1$ (which is set to $b_6 \cdot b_5$) to reduce the degree by $1$. Thus, the degree of op flags in this group is $4$. + +How operations are distributed between these $4$ groups is described in the sections below. + +### No stack shift operations +This group contains $32$ operations which do not shift the stack (this is almost all such operations). Since the op flag degree for these operations is $7$, constraints for these operations cannot exceed degree $2$. + +| Operation | Opcode value | Binary encoding | Operation group | Flag degree | +|-----------|:------------:|:---------------:|:-----------------------------:|:-----------:| +| `NOOP` | $0$ | `000_0000` | [System ops](./system_ops.md) | $7$ | +| `EQZ ` | $1$ | `000_0001` | [Field ops](./field_ops.md) | $7$ | +| `NEG` | $2$ | `000_0010` | [Field ops](./field_ops.md) | $7$ | +| `INV` | $3$ | `000_0011` | [Field ops](./field_ops.md) | $7$ | +| `INCR` | $4$ | `000_0100` | [Field ops](./field_ops.md) | $7$ | +| `NOT` | $5$ | `000_0101` | [Field ops](./field_ops.md) | $7$ | +| ``| $6$ | `000_0110` | | $7$ | +| `MLOAD` | $7$ | `000_0111` | [I/O ops](./io_ops.md) | $7$ | +| `SWAP` | $8$ | `000_1000` | [Stack ops](./stack_ops.md) | $7$ | +| `CALLER` | $9$ | `000_1001` | [System ops](./system_ops.md) | $7$ | +| `MOVUP2` | $10$ | `000_1010` | [Stack ops](./stack_ops.md) | $7$ | +| `MOVDN2` | $11$ | `000_1011` | [Stack ops](./stack_ops.md) | $7$ | +| `MOVUP3` | $12$ | `000_1100` | [Stack ops](./stack_ops.md) | $7$ | +| `MOVDN3` | $13$ | `000_1101` | [Stack ops](./stack_ops.md) | $7$ | +| `ADVPOPW` | $14$ | `000_1110` | [I/O ops](./io_ops.md) | $7$ | +| `EXPACC` | $15$ | `000_1111` | [Field ops](./field_ops.md) | $7$ | +| `MOVUP4` | $16$ | `001_0000` | [Stack ops](./stack_ops.md) | $7$ | +| `MOVDN4` | $17$ | `001_0001` | [Stack ops](./stack_ops.md) | $7$ | +| `MOVUP5` | $18$ | `001_0010` | [Stack ops](./stack_ops.md) | $7$ | +| `MOVDN5` | $19$ | `001_0011` | [Stack ops](./stack_ops.md) | $7$ | +| `MOVUP6` | $20$ | `001_0100` | [Stack ops](./stack_ops.md) | $7$ | +| `MOVDN6` | $21$ | `001_0101` | [Stack ops](./stack_ops.md) | $7$ | +| `MOVUP7` | $22$ | `001_0110` | [Stack ops](./stack_ops.md) | $7$ | +| `MOVDN7` | $23$ | `001_0111` | [Stack ops](./stack_ops.md) | $7$ | +| `SWAPW` | $24$ | `001_1000` | [Stack ops](./stack_ops.md) | $7$ | +| `EXT2MUL` | $25$ | `001_1001` | [Field ops](./field_ops.md) | $7$ | +| `MOVUP8` | $26$ | `001_1010` | [Stack ops](./stack_ops.md) | $7$ | +| `MOVDN8` | $27$ | `001_1011` | [Stack ops](./stack_ops.md) | $7$ | +| `SWAPW2` | $28$ | `001_1100` | [Stack ops](./stack_ops.md) | $7$ | +| `SWAPW3` | $29$ | `001_1101` | [Stack ops](./stack_ops.md) | $7$ | +| `SWAPDW` | $30$ | `001_1110` | [Stack ops](./stack_ops.md) | $7$ | +| `EMIT` | $31$ | `001_1111` | [System ops](./system_ops.md) | $7$ | + +### Left stack shift operations +This group contains $16$ operations which shift the stack to the left (i.e., remove an item from the stack). Most of left-shift operations are contained in this group. Since the op flag degree for these operations is $7$, constraints for these operations cannot exceed degree $2$. + +| Operation | Opcode value | Binary encoding | Operation group | Flag degree | +| ----------- | :----------: | :-------------: | :---------------------------: | :---------: | +| `ASSERT` | $32$ | `010_0000` | [System ops](./system_ops.md) | $7$ | +| `EQ` | $33$ | `010_0001` | [Field ops](./field_ops.md) | $7$ | +| `ADD` | $34$ | `010_0010` | [Field ops](./field_ops.md) | $7$ | +| `MUL` | $35$ | `010_0011` | [Field ops](./field_ops.md) | $7$ | +| `AND` | $36$ | `010_0100` | [Field ops](./field_ops.md) | $7$ | +| `OR` | $37$ | `010_0101` | [Field ops](./field_ops.md) | $7$ | +| `U32AND` | $38$ | `010_0110` | [u32 ops](./u32_ops.md) | $7$ | +| `U32XOR` | $39$ | `010_0111` | [u32 ops](./u32_ops.md) | $7$ | +| `FRIE2F4` | $40$ | `010_1000` | [Crypto ops](./crypto_ops.md) | $7$ | +| `DROP` | $41$ | `010_1001` | [Stack ops](./stack_ops.md) | $7$ | +| `CSWAP` | $42$ | `010_1010` | [Stack ops](./stack_ops.md) | $7$ | +| `CSWAPW` | $43$ | `010_1011` | [Stack ops](./stack_ops.md) | $7$ | +| `MLOADW` | $44$ | `010_1100` | [I/O ops](./io_ops.md) | $7$ | +| `MSTORE` | $45$ | `010_1101` | [I/O ops](./io_ops.md) | $7$ | +| `MSTOREW` | $46$ | `010_1110` | [I/O ops](./io_ops.md) | $7$ | +| `` | $47$ | `010_1111` | | $7$ | + +### Right stack shift operations +This group contains $16$ operations which shift the stack to the right (i.e., push a new item onto the stack). Most of right-shift operations are contained in this group. Since the op flag degree for these operations is $7$, constraints for these operations cannot exceed degree $2$. + +| Operation | Opcode value | Binary encoding | Operation group | Flag degree | +| --------- | :----------: | :-------------: | :---------------------------: | :---------: | +| `PAD` | $48$ | `011_0000` | [Stack ops](./stack_ops.md) | $7$ | +| `DUP` | $49$ | `011_0001` | [Stack ops](./stack_ops.md) | $7$ | +| `DUP1` | $50$ | `011_0010` | [Stack ops](./stack_ops.md) | $7$ | +| `DUP2` | $51$ | `011_0011` | [Stack ops](./stack_ops.md) | $7$ | +| `DUP3` | $52$ | `011_0100` | [Stack ops](./stack_ops.md) | $7$ | +| `DUP4` | $53$ | `011_0101` | [Stack ops](./stack_ops.md) | $7$ | +| `DUP5` | $54$ | `011_0110` | [Stack ops](./stack_ops.md) | $7$ | +| `DUP6` | $55$ | `011_0111` | [Stack ops](./stack_ops.md) | $7$ | +| `DUP7` | $56$ | `011_1000` | [Stack ops](./stack_ops.md) | $7$ | +| `DUP9` | $57$ | `011_1001` | [Stack ops](./stack_ops.md) | $7$ | +| `DUP11` | $58$ | `011_1010` | [Stack ops](./stack_ops.md) | $7$ | +| `DUP13` | $59$ | `011_1011` | [Stack ops](./stack_ops.md) | $7$ | +| `DUP15` | $60$ | `011_1100` | [Stack ops](./stack_ops.md) | $7$ | +| `ADVPOP` | $61$ | `011_1101` | [I/O ops](./io_ops.md) | $7$ | +| `SDEPTH` | $62$ | `011_1110` | [I/O ops](./io_ops.md) | $7$ | +| `CLK` | $63$ | `011_1111` | [System ops](./system_ops.md) | $7$ | + +### u32 operations +This group contains $8$ u32 operations. These operations are grouped together because all of them require range checks. The constraints for range checks are of degree $5$, however, since all these operations require them, we can define a flag with common prefix `100` to serve as a selector for the range check constraints. The value of this flag is computed as follows: + +$$ +f_{u32rc} = b_6 \cdot (1 - b_5) \cdot (1 - b_4) +$$ + +The degree of this flag is $3$, which is acceptable for a selector for degree $5$ constraints. + +| Operation | Opcode value | Binary encoding | Operation group | Flag degree | +| ------------ | :----------: | :-------------: | :---------------------: | :---------: | +| `U32ADD` | $64$ | `100_0000` | [u32 ops](./u32_ops.md) | $6$ | +| `U32SUB` | $66$ | `100_0010` | [u32 ops](./u32_ops.md) | $6$ | +| `U32MUL` | $68$ | `100_0100` | [u32 ops](./u32_ops.md) | $6$ | +| `U32DIV` | $70$ | `100_0110` | [u32 ops](./u32_ops.md) | $6$ | +| `U32SPLIT` | $72$ | `100_1000` | [u32 ops](./u32_ops.md) | $6$ | +| `U32ASSERT2` | $74$ | `100_1010` | [u32 ops](./u32_ops.md) | $6$ | +| `U32ADD3` | $76$ | `100_1100` | [u32 ops](./u32_ops.md) | $6$ | +| `U32MADD` | $78$ | `100_1110` | [u32 ops](./u32_ops.md) | $6$ | + +As mentioned previously, the last bit of the opcode is not used in computation of the flag for these operations. We force this bit to always be set to $0$ with the following constraint: + +$$ +b_6 \cdot (1 - b_5) \cdot (1 - b_4) \cdot b_0 = 0 \text{ | degree} = 4 +$$ + +Putting these operations into a group with flag degree $6$ is important for two other reasons: +* Constraints for the `U32SPLIT` operation have degree $3$. Thus, the degree of the op flag for this operation cannot exceed $6$. +* Operations `U32ADD3` and `U32MADD` shift the stack to the left. Thus, having these two operations in this group and putting them under the common prefix `10011` allows us to create a common flag for these operations of degree $5$ (recall that the left-shift flag cannot exceed degree $5$). + +### High-degree operations +This group contains operations which require constraints with degree up to $3$. All $7$ operation bits are used for these flags. The extra $e_0$ column is used for degree reduction of the three high-degree bits. + +| Operation | Opcode value | Binary encoding | Operation group | Flag degree | +|---------------|:------------:|:---------------:|:--------------------------------------:|:-----------:| +| `HPERM` | $80$ | `101_0000` | [Crypto ops](./crypto_ops.md) | $5$ | +| `MPVERIFY` | $81$ | `101_0001` | [Crypto ops](./crypto_ops.md) | $5$ | +| `PIPE` | $82$ | `101_0010` | [I/O ops](./io_ops.md) | $5$ | +| `MSTREAM` | $83$ | `101_0011` | [I/O ops](./io_ops.md) | $5$ | +| `SPLIT` | $84$ | `101_0100` | [Flow control ops](../decoder/index.md) | $5$ | +| `LOOP` | $85$ | `101_0101` | [Flow control ops](../decoder/index.md) | $5$ | +| `SPAN` | $86$ | `101_0110` | [Flow control ops](../decoder/index.md) | $5$ | +| `JOIN` | $87$ | `101_0111` | [Flow control ops](../decoder/index.md) | $5$ | +| `DYN` | $88$ | `101_1000` | [Flow control ops](../decoder/index.md) | $5$ | +| `HORNEREXT` | $89$ | `101_1001` | [Crypto ops](./crypto_ops.md) | $5$ | +| `LOGPRECOMPILE` | $90$ | `101_1010` | [Crypto ops](./crypto_ops.md#log_precompile) | $5$ | +| `PUSH` | $91$ | `101_1011` | [I/O ops](./io_ops.md) | $5$ | +| `DYNCALL` | $92$ | `101_1100` | [Flow control ops](../decoder/index.md) | $5$ | +| `EVALCIRCUIT` | $93$ | `101_1101` | [Crypto ops](./crypto_ops.md) | $5$ | +| `` | $94$ | `101_1110` | | $5$ | +| `` | $95$ | `101_1111` | | $5$ | + +Note that the `SPLIT` and `LOOP` operations are grouped together under the common prefix `101010`, and thus can have a common flag of degree $4$ (using $e_0$ for degree reduction). This is important because both of these operations shift the stack to the left. + + +Also, we need to make sure that `extra` register $e_0$, which is used to reduce the flag degree by $2$, is set to $1$ when $b_6 = 1$, $b_5 = 0$, and $b_4 = 1$: + +$$ +e_0 - b_6 \cdot (1 - b_5) \cdot b_4 = 0 \text{ | degree} = 3 +$$ + +### Very high-degree operations +This group contains operations which require constraints with degree up to $5$. + +| Operation | Opcode value | Binary encoding | Operation group | Flag degree | +| ------------ | :----------: | :-------------: | :------------------------------------: | :---------: | +| `MRUPDATE` | $96$ | `110_0000` | [Crypto ops](./crypto_ops.md) | $4$ | +| `HORNERBASE` | $100$ | `110_0100` | [Crypto ops](./crypto_ops.md) | $4$ | +| `SYSCALL` | $104$ | `110_1000` | [Flow control ops](../decoder/index.md) | $4$ | +| `CALL` | $108$ | `110_1100` | [Flow control ops](../decoder/index.md) | $4$ | +| `END` | $112$ | `111_0000` | [Flow control ops](../decoder/index.md) | $4$ | +| `REPEAT` | $116$ | `111_0100` | [Flow control ops](../decoder/index.md) | $4$ | +| `RESPAN` | $120$ | `111_1000` | [Flow control ops](../decoder/index.md) | $4$ | +| `HALT` | $124$ | `111_1100` | [Flow control ops](../decoder/index.md) | $4$ | + +As mentioned previously, the last two bits of the opcode are not used in computation of the flag for these operations. We force these bits to always be set to $0$ with the following constraints: + +$$ +b_6 \cdot b_5 \cdot b_0 = 0 \text{ | degree} = 3 +$$ + +$$ +b_6 \cdot b_5 \cdot b_1 = 0 \text{ | degree} = 3 +$$ + +Also, we need to make sure that `extra` register $e_1$, which is used to reduce the flag degree by $1$, is set to $1$ when both $b_6$ and $b_5$ columns are set to $1$: + +$$ +e_1 - b_6 \cdot b_5 = 0 \text{ | degree} = 2 +$$ + +## Composite flags +Using the operation flags defined above, we can compute several composite flags which are used by various constraints in the VM. + +### Shift right flag +The right-shift flag indicates that an operation shifts the stack to the right. This flag is computed as follows: + +$$ +f_{shr} = (1 - b_6) \cdot b_5 \cdot b_4 + f_{u32split} + f_{push} \text{ | degree} = 6 +$$ + +In the above, $(1 - b_6) \cdot b_5 \cdot b_4$ evaluates to $1$ for all [right stack shift](#right-stack-shift-operations) operations described previously. This works because all these operations have a common prefix `011`. We also need to add in flags for other operations which shift the stack to the right but are not a part of the above group (e.g., `PUSH` operation). + +### Shift left flag +The left-shift flag indicates that a given operation shifts the stack to the left. To simplify the description of this flag, we will first compute the following intermediate variables: + +A flag which is set to $1$ when $f_{u32add3} = 1$ or $f_{u32madd} = 1$: + +$$ +f_{add3\_madd} = b_6 \cdot (1 - b_5) \cdot (1 - b_4) \cdot b_3 \cdot b_2 \text{ | degree} = 5 +$$ + +A flag which is set to $1$ when $f_{split} = 1$ or $f_{loop} = 1$: + +$$ +f_{split\_loop} = e_0 \cdot (1 - b_3) \cdot b_2 \cdot (1 - b_1) \text{ | degree} = 4 +$$ + +Using the above variables, we compute left-shift flag as follows: + +$$ +f_{shl} = (1 - b_6) \cdot b_5 \cdot (1 - b_4) + f_{add3\_madd} + f_{split\_loop} + f_{repeat} + f_{end} \cdot h_5 \text{ | degree} = 5 +$$ + +In the above: +* $(1 - b_6) \cdot b_5 \cdot (1 - b_4)$ evaluates to $1$ for all [left stack shift](#left-stack-shift-operations) operations described previously. This works because all these operations have a common prefix `010`. +* $h_5$ is the helper register in the decoder which is set to $1$ when we are exiting a `LOOP` block, and to $0$ otherwise. + +Thus, similarly to the right-shift flag, we compute the value of the left-shift flag based on the prefix of the operation group which contains most left shift operations, and add in flag values for other operations which shift the stack to the left but are not a part of this group. + +### Control flow flag +The control flow flag $f_{ctrl}$ is set to $1$ when a control flow operation is being executed by the VM, and to $0$ otherwise. Naively, this flag can be computed as follows: + +$$ +f_{ctrl} = f_{join} + f_{split} + f_{loop} + f_{repeat} + f_{span} + f_{respan} + f_{call} + f_{syscall} + f_{end} + f_{halt} \text{ | degree} = 6 +$$ + +However, this can be computed more efficiently via the common operation prefixes for the two groups of control flow operations as follows. + +$$ +f_{span,join,split,loop} = e_0 \cdot (1 - b_3) \cdot b_2 \text{ | degree} = 3 +$$ + +$$ +f_{end,repeat,respan,halt} = e_1 \cdot b_4 \text{ | degree} = 2 +$$ + +$$ +f_{ctrl} = f_{span,join,split,loop} + f_{end,repeat,respan,halt} + f_{dyn} + f_{call} + f_{syscall} \text{ | degree} = 5 +$$ + +### Immediate value flag + +The immediate value flag $f_{imm}$ is set to 1 when an operation has an immediate value, and 0 otherwise: + +$$ +f_{imm} = f_{push} \text{ | degree} = 5 +$$ + +Note that the `ASSERT`, `MPVERIFY` and other operations have immediate values too. However, these immediate values are not included in the MAST digest, and hence are not considered for the $f_{imm}$ flag. diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/design/stack/precompiles.md b/versioned_docs/version-0.12 (stable)/miden-vm/design/stack/precompiles.md new file mode 100644 index 0000000..c208330 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-vm/design/stack/precompiles.md @@ -0,0 +1,79 @@ +# Precompiles + +Precompiles let Miden programs defer expensive computations to the host while still producing +auditable evidence inside the STARK. This page describes how the VM, host, prover, and verifier +coordinate to maintain a sequential commitment to every precompile invocation. + +## Core data + +| Concept | Description | +| ------- | ----------- | +| `PrecompileRequest` | Minimal calldata for a precompile, recorded by the host when the event handler runs. It contains exactly the information needed to deterministically recompute the result and the commitment. Requests are included in the proof artifact. | +| `PrecompileCommitment` | A word pair `(TAG, COMM)` computed by the MASM wrapper, and deterministically recomputable from the corresponding `PrecompileRequest`. `COMM` typically commits to inputs, and may also include outputs for long results; the three free elements in `TAG` carry metadata and/or simple results. Together `(TAG, COMM)` represent the full request (inputs + outputs). | +| `PrecompileTranscript` | A sequential commitment to all precompile requests. Implemented with an RPO256 sponge; the VM stores only the capacity (4 elements). The verifier reconstructs the same transcript by re‑evaluating requests and their commitments. Finalizing yields a transcript digest. | + +## Lifecycle overview + +1. **Wrapper emits event** – The MASM wrapper stages inputs (e.g., on stack/memory) and emits the event for the target precompile. +2. **Host handler runs** – The host executes the event handler, reads required inputs from the current process state, stores a `PrecompileRequest` (raw calldata) for later verification, and pushes the precompile result to the VM via the advice stack. +3. **Wrapper constructs commitment** – The wrapper pops result(s) from advice, computes `(TAG, COMM)` per the precompile’s convention, and prepares to log the operation. +4. **`log_precompile` records the commitment** – The wrapper invokes `log_precompile` with `[COMM, TAG, PAD, ...]`. The instruction: + - Reads the previous transcript capacity `CAP_PREV` (non‑deterministically via helper registers). + - Applies the RPO permutation to `[CAP_PREV, TAG, COMM]`, producing `[CAP_NEXT, R0, R1]`. + - Writes `[R1, R0, CAP_NEXT]` back onto the stack; programs typically drop these words immediately. +5. **Capacity tracking via vtable** – Capacity is tracked inside the VM via the chiplets’ virtual table; the host never tracks capacity. The table always stores the current capacity (the transcript state). On each `log_precompile`: + - The previous capacity is removed from the table. + - The permutation links `CAP_PREV --[TAG,COMM]--> CAP_NEXT`. + - The next capacity is inserted back into the table. + This enforces that updates can only occur by applying the permutation. +6. **Trace output and proof** – The capacity state is used to construct the vtable auxiliary column, while the prover stores only the ordered `PrecompileRequest`s in the proof. +7. **Verifier reconstruction** – The verifier replays each request via a `PrecompileVerifier` to recompute `(TAG, COMM)`, records them into a fresh transcript, and enforces the initial/final capacity via public inputs. To check correct linking, the verifier initializes the column with an initial insertion of the empty capacity and a removal of the final capacity; the final capacity is provided as a public input to the AIR. +8. **Finalization convention** – When a digest is needed, finalize the transcript by absorbing two empty words (zeros in the rate) and permuting once. The transcript digests the ordered sequence of `[TAG, COMM]` words for all requests; `log_precompile` discards rate outputs (`R0`, `R1`), so only the capacity persists. + +## Responsibilities + +| Participant | Responsibilities | +| ----------- | ---------------- | +| VM | Executes `log_precompile`, maintains the capacity word internally, and participates in capacity initialization via the chiplets’ virtual table. | +| Host | Executes the event handler, reads inputs from process state, stores `PrecompileRequest`, and returns the result via the advice provider (typically the advice stack; map/Merkle store as needed). | +| MASM wrapper | Collects inputs and emits the event; pops results from advice; computes `(TAG, COMM)`; invokes `log_precompile`. | +| Prover | Includes the precompile requests in the proof. | +| Verifier | Replays requests via registered verifiers, rebuilds the transcript, enforces the initial/final capacity via variable‑length public inputs, and finalizes to a digest if needed. | + +## Conventions + +- Tag layout: `TAG = [event_id, meta1, meta2, meta3]`. + - First element is the precompile’s `event_id`. + - The remaining three elements carry metadata or simple results: + - Examples: byte length of inputs; boolean validity of a signature; flag bits. +- Commitment layout: `COMM` + - Typically commits to inputs. + - May also include outputs when results are long, so that `(TAG, COMM)` together represent the full request (inputs + outputs). + - The exact composition is precompile‑specific and defined by its verifier specification. +- `log_precompile` stack effect: `[COMM, TAG, PAD, ...] -> [R1, R0, CAP_NEXT, ...]` where + `RPO([CAP_PREV, TAG, COMM]) = [CAP_NEXT, R0, R1]`. + +- Input encoding: + - By convention, inputs are encoded as packed u32 values in field elements (4 bytes per element, little‑endian). If the input length is not a multiple of 4, the final u32 is zero‑padded. Because of this packing, wrappers commonly include the byte length in `TAG` to distinguish data bytes from padding. + +## Examples + +- Hash function + - Inputs: byte sequence at a given memory location; Output: digest (long). + - Wrapper emits the event; handler reads memory and returns digest via advice; wrapper computes: + - `TAG = [event_id, len_bytes, 0, 0]` + - `COMM = Rpo256( Rpo256(input_words) || Rpo256(digest_words) )` (bind input and digest) + - Wrapper calls `log_precompile` with `[COMM, TAG, PAD, ...]` and drops the outputs. + +- Signature scheme + - Inputs: public key, message (or prehash), signature; may include flag bits indicating special operation options. Output: `is_valid` (boolean). + - Wrapper emits the event; handler verifies and may push auxiliary results; wrapper computes: + - `TAG = [event_id, is_valid, flags, 0]` (encode simple result and flags) + - `COMM = Rpo256( prepared_inputs[..] )` (inputs‑only is typical when outputs are simple) + - Wrapper calls `log_precompile` to record the request commitment and result tag. + +## Related reading + +- [`log_precompile` instruction](../../user_docs/assembly/instruction_reference.md) – stack behaviour and semantics. +- `PrecompileTranscript` implementation (`core/src/precompile.rs`) – transcript details in the codebase. +- Kernel ROM chiplet initialization pattern (`../chiplets/kernel_rom.md`) – example use of variable‑length public inputs to initialize a chiplet/aux column via the bus. diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/design/stack/stack_ops.md b/versioned_docs/version-0.12 (stable)/miden-vm/design/stack/stack_ops.md new file mode 100644 index 0000000..b24595c --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-vm/design/stack/stack_ops.md @@ -0,0 +1,234 @@ +--- +title: "Stack Manipulation" +sidebar_position: 6 +--- + +# Stack Manipulation +In this section we describe the AIR constraints for Miden VM stack manipulation operations. + +## PAD +The `PAD` operation pushes a $0$ onto the stack. The diagram below illustrates this graphically. + +![pad](../../img/design/stack/stack_ops/PAD.png) + +Stack transition for this operation must satisfy the following constraints: + +$$ +s_{0}' = 0 \text{ | degree} = 1 +$$ + +The effect of this operation on the rest of the stack is: +* **Right shift** starting from position $0$. + +## DROP +The `DROP` operation removes an element from the top of the stack. The diagram below illustrates this graphically. + +![drop](../../img/design/stack/stack_ops/DROP.png) + +The `DROP` operation shifts the stack by $1$ element to the left, but does not impose any additional constraints. The degree of left shift constraints is $1$. + +The effect of this operation on the rest of the stack is: +* **Left shift** starting from position $1$. + + +## DUP(n) +The `DUP(n)` operations push a copy of the $n$-th stack element onto the stack. Eg. `DUP` (same as `DUP0`) pushes a copy of the top stack element onto the stack. Similarly, `DUP5` pushes a copy of the $6$-th stack element onto the stack. This operation is valid for $n \in \{0, ..., 7, 9, 11, 13, 15\}$. The diagram below illustrates this graphically. + +![dupn](../../img/design/stack/stack_ops/DUP(n).png) + +Stack transition for this operation must satisfy the following constraints: + +$$ +s_{0}' - s_{n} = 0 \text{ for } n \in \{0, ..., 7, 9, 11, 13, 15\} \text{ | degree} = 1 +$$ + +where $n$ is the depth of the stack from where the element has been copied. + +The effect of this operation on the rest of the stack is: +* **Right shift** starting from position $0$. + +## SWAP +The `SWAP` operations swaps the top two elements of the stack. The diagram below illustrates this graphically. + +![swap](../../img/design/stack/stack_ops/SWAP.png) + +Stack transition for this operation must satisfy the following constraints: + +$$ +s_{0}' - s_{1} = 0 \text{ | degree} = 1 +$$ + +$$ +s_{1}' - s_{0} = 0 \text{ | degree} = 1 +$$ + +The effect of this operation on the rest of the stack is: +* **No change** starting from position $2$. + +## SWAPW +The `SWAPW` operation swaps stack elements $0, 1, 2, 3$ with elements $4, 5, 6, 7$. The diagram below illustrates this graphically. + +![swapw](../../img/design/stack/stack_ops/SWAPW.png) + +Stack transition for this operation must satisfy the following constraints: + +$$ +s_{i}' - s_{i+4} = 0 \text{ for } i \in \{0, 1, 2, 3\} \text{ | degree} = 1 +$$ + +$$ +s_{i + 4}' - s_i = 0 \text{ for } i \in \{0, 1, 2, 3\} \text{ | degree} = 1 +$$ + +The effect of this operation on the rest of the stack is: +* **No change** starting from position $8$. + +## SWAPW2 +The `SWAPW2` operation swaps stack elements $0, 1, 2, 3$ with elements $8, 9, 10, 11$. The diagram below illustrates this graphically. + +![swapw2](../../img/design/stack/stack_ops/SWAPW2.png) + +Stack transition for this operation must satisfy the following constraints: + +$$ +s_i' - s_{i+8} = 0 \text{ for } i \in \{0, 1, 2, 3\} \text{ | degree} = 1 +$$ + +$$ +s_{i + 8}' - s_i = 0 \text{ for } i \in \{0, 1, 2, 3\} \text{ | degree} = 1 +$$ + +The effect of this operation on the rest of the stack is: +* **No change** for elements $4, 5, 6, 7$. +* **No change** starting from position $12$. + +## SWAPW3 +The `SWAPW3` operation swaps stack elements $0, 1, 2, 3$ with elements $12, 13, 14, 15$. The diagram below illustrates this graphically. + +![swapw3](../../img/design/stack/stack_ops/SWAPW3.png) + +Stack transition for this operation must satisfy the following constraints: + +$$ +s_i' - s_{i+12} = 0 \text{ for } i \in \{0, 1, 2, 3\} \text{ | degree} = 1 +$$ + +$$ +s_{i+12}' - s_i = 0 \text{ for } i \in \{0, 1, 2, 3\} \text{ | degree} = 1 +$$ + +The effect of this operation on the rest of the stack is: +* **No change** for elements $4, 5, 6, 7, 8, 9, 10, 11$. +* **No change** starting from position $16$. + +## SWAPDW +The `SWAPDW` operation swaps stack elements $[0, 8)$ with elements $[8, 16)$. The diagram below illustrates this graphically. + +![swapdw](../../img/design/stack/stack_ops/SWAPDW.png) + +Stack transition for this operation must satisfy the following constraints: + +$$ +s_i' - s_{i+8} = 0 \text{ for } i \in [0, 8) \text{ | degree} = 1 +$$ + +$$ +s_{i+8}' - s_i = 0 \text{ for } i \in [0, 8) \text{ | degree} = 1 +$$ + +The effect of this operation on the rest of the stack is: +* **No change** starting from position $16$. + +## MOVUP(n) +The `MOVUP(n)` operation moves the $n$-th element of the stack to the top of the stack. For example, `MOVUP2` moves element at depth $2$ to the top of the stack. All elements with depth less than $n$ are shifted to the right by one, while elements with depth greater than $n$ remain in place, and the depth of the stack does not change. This operation is valid for $n \in [2, 9)$. The diagram below illustrates this graphically. + +![movup](../../img/design/stack/stack_ops/MOVUP(n).png) + +Stack transition for this operation must satisfy the following constraints: + +$$ +s_0' - s_n = 0 \text{ for } n \in [2, 9) \text{ | degree} = 1 +$$ + +where $n$ is the depth of the element which is moved moved to the top of the stack. + +The effect of this operation on the rest of the stack is: +* **Right shift** for elements between $0$ and $n-1$. +* **No change** starting from position $n+1$. + +## MOVDN(n) +The `MOVDN(n)` operation moves the top element of the stack to the $n$-th position. For example, `MOVDN2` moves the top element of the stack to depth $2$. All the elements with depth less than $n$ are shifted to the left by one, while elements with depth greater than $n$ remain in place, and the depth of the stack does not change. This operation is valid for $n \in [2, 9)$. The diagram below illustrates this graphically. + +![movdn](../../img/design/stack/stack_ops/MOVDN(n).png) + +Stack transition for this operation must satisfy the following constraints: + +$$ +s_n' - s_0 = 0 \text{ for } n \in [2, 9) \text{ | degree} = 1 +$$ + +where $n$ is the depth to which the top stack element is moved. + +The effect of this operation on the rest of the stack is: +* **Left shift** for elements between $1$ and $n$. +* **No change** starting from position $n+1$. + +## CSWAP +The `CSWAP` operation pops an element off the stack and if the element is $1$, swaps the top two remaining elements. If the popped element is $0$, the rest of the stack remains unchanged. The diagram below illustrates this graphically. + +![cswap](../../img/design/stack/stack_ops/CSWAP.png) + +In the above: + +$$ +d = \begin{cases} a, & \text{if}\ c = 0 b, & \text{if}\ c = 1\ \end{cases} e = \begin{cases} b, & \text{if}\ c = 0 a, & \text{if}\ c = 1\ \end{cases} +$$ + +Stack transition for this operation must satisfy the following constraints: + +$$ +s_0' - s_{0} \cdot s_{2} - (1-s_0) \cdot s_1 = 0 \text{ | degree} = 2 +$$ + +$$ +s_1' - s_0 \cdot s_{1} - (1-s_0) \cdot s_2 = 0 \text{ | degree} = 2 +$$ + +We also need to enforce that the value in $s_0$ is binary. This can be done with the following constraint: + +$$ +s_0^2 - s_0 = 0 \text{ | degree} = 2 +$$ + +The effect of this operation on the rest of the stack is: +* **Left shift** starting from position $3$. + +## CSWAPW +The `CSWAPW` operation pops an element off the stack and if the element is $1$, swaps elements $1, 2, 3, 4$ with elements $5, 6, 7, 8$. If the popped element is $0$, the rest of the stack remains unchanged. The diagram below illustrates this graphically. + +![cswapw](../../img/design/stack/stack_ops/CSWAPW.png) + +In the above: + +$$ +D = \begin{cases} A, & \text{if}\ c = 0 B, & \text{if}\ c = 1\ \end{cases} E = \begin{cases} B, & \text{if}\ c = 0 A, & \text{if}\ c = 1\ \end{cases} +$$ + +Stack transition for this operation must satisfy the following constraints: + +$$ +s_i' - s_0 \cdot s_{i+5} - (1-s_0) \cdot s_{i+1} = 0 \text{ for } i \in [0, 4) \text{ | degree} = 2 +$$ + +$$ +s_{i+4}' - s_0 \cdot s_{i+1} - (1-s_0) \cdot s_{i+5} = 0 \text{ for } i \in [0, 4) \text{ | degree} = 2 +$$ + +We also need to enforce that the value in $s_0$ is binary. This can be done with the following constraint: + +$$ +s_0^2 - s_0 = 0 \text{ | degree} = 2 +$$ + +The effect of this operation on the rest of the stack is: +* **Left shift** starting from position $9$. diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/design/stack/system_ops.md b/versioned_docs/version-0.12 (stable)/miden-vm/design/stack/system_ops.md new file mode 100644 index 0000000..94681e9 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-vm/design/stack/system_ops.md @@ -0,0 +1,49 @@ +--- +title: "System Operations" +sidebar_position: 3 +--- + +# System Operations +In this section we describe the AIR constraints for Miden VM system operations. + +## NOOP +The `NOOP` operation advances the cycle counter but does not change the state of the operand stack (i.e., the depth of the stack and the values on the stack remain the same). + +The `NOOP` operation does not impose any constraints besides the ones needed to ensure that the entire state of the stack is copied over. This constraint looks like so: + +$$ +s'_i - s_i = 0 \ \text{ for } i \in [0, 16) \text { | degree} = 1 +$$ + +## EMIT +The `EMIT` operation interrupts execution for a single cycle and hands control to the host. During this interruption, the host can read the current state of the execution and modify the advice provider as it sees fit. From the VM's perspective, this operation has exactly the same semantics as [`NOOP`](#noop) - the operand stack remains completely unchanged. + +By convention, the top element of the stack is used to encode an event ID (see the [events documentation](../../user_docs/assembly/events.md) for details on event structure and usage). The host can use this event ID to determine what actions to take during the execution interruption. + +## ASSERT +The `ASSERT` operation pops an element off the stack and checks if the popped element is equal to $1$. If the element is not equal to $1$, program execution fails. + +![assert](../../img/design/stack/system_ops/ASSERT.png) + +Stack transition for this operation must satisfy the following constraints: + +$$ +s_0 - 1 = 0 \text{ | degree} = 1 +$$ + +The effect on the rest of the stack is: +* **Left shift** starting from position $1$. + +## CLK +The `CLK` operation pushes the current value of the clock cycle onto the stack. The diagram below illustrates this graphically. + +![clk](../../img/design/stack/system_ops/CLK.png) + +The stack transition for this operation must follow the following constraint: + +$$ +s_0' - clk = 0 \text{ | degree} = 1 +$$ + +The effect on the rest of the stack is: +* **Right shift** starting from position $0$. diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/design/stack/u32_ops.md b/versioned_docs/version-0.12 (stable)/miden-vm/design/stack/u32_ops.md new file mode 100644 index 0000000..fe7c9ee --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-vm/design/stack/u32_ops.md @@ -0,0 +1,285 @@ +--- +title: "u32 Operations" +sidebar_position: 5 +--- + +# u32 Operations +In this section we describe semantics and AIR constraints of operations over u32 values (i.e., 32-bit unsigned integers) as they are implemented in Miden VM. + +### Range checks +Most operations described below require some number of 16-bit range checks (i.e., verifying that the value of a field element is smaller than $2^{16}$). The number of required range checks varies between $2$ and $4$, depending on the operation. However, to simplify the constraint system, we force each relevant operation to consume exactly $4$ range checks. + +To perform these range checks, the prover puts the values to be range-checked into helper registers $h_0, ..., h_3$, and then updates the range checker bus column $b_{range}$ according to the LogUp construction described in the [range checker](../range.md) documentation, using multiplicity $1$ for each value. + +This operation is enforced via the following constraint. Note that since constraints cannot include divisions, the actual constraint which is enforced will be expressed equivalently with all denominators multiplied through, resulting in a constraint of degree 5. + +$$ +b_{range}' = b_{range} - \frac{1}{(\alpha - h_0)} - \frac{1}{(\alpha - h_1)} - \frac{1}{(\alpha - h_2)} - \frac{1}{(\alpha - h_3)} \text{ | degree} = 5 +$$ + +The above is just a partial constraint as it does not show the range checker's part of the constraint, which adds the required values into the bus column. It also omits the [selector flag](./op_constraints.md#operation-flags) which is used to turn this constraint on only when executing relevant operations. + +### Checking element validity +Another primitive which is required by most of the operations described below is checking whether four 16-bit values form a valid field element. Assume $t_0$, $t_1$, $t_2$, and $t_3$ are known to be 16-bit values, and we want to verify that $2^{48} \cdot t_3 + 2^{32} \cdot t_2 + 2^{16} \cdot t_1 + t_0$ is a valid field element. + +For simplicity, let's denote: + +$$ +v_{hi} = 2^{16} \cdot t_3 + t_2 +v_{lo} = 2^{16} \cdot t_1 + t_0 +$$ + +We can then impose the following constraint to verify element validity: + +> $$ +> \left(1 - m \cdot (2^{32} - 1 - v_{hi})\right) \cdot v_{lo} = 0 \text{ | degree} = 3 +> $$ + +Where $m$ is a value set non-deterministically by the prover. + +The above constraint should hold only if either of the following hold: + +* $v_{lo} = 0$ +* $v_{hi} \ne 2^{32} - 1$ + +To satisfy the latter equation, the prover needs to set $m = (2^{32} - 1 - v_{hi})^{-1}$, which is possible only when $v_{hi} \ne 2^{32} - 1$. + +This constraint is sufficient because modulus $2^{64} - 2^{32} + 1$ in binary representation is 32 ones, followed by 31 zeros, followed by a single one: + +$$ +1111111111111111111111111111111100000000000000000000000000000001 +$$ + +This implies that the largest possible 64-bit value encoding a valid field element would be 32 ones, followed by 32 zeros: + +$$ +1111111111111111111111111111111100000000000000000000000000000000 +$$ + +Thus, for a 64-bit value to encode a valid field element, either the lower 32 bits must be all zeros, or the upper 32 bits must not be all ones (which is $2^{32} - 1$). + +## U32SPLIT +Assume $a$ is the element at the top of the stack. The `U32SPLIT` operation computes $(b,c) \leftarrow a$, where $b$ contains the lower 32 bits of $a$, and $c$ contains the upper 32 bits of $a$. The diagram below illustrates this graphically. + +![u32split](../../img/design/stack/u32_operations/U32SPLIT.png) + +To facilitate this operation, the prover sets values in $h_0, ..., h_3$ to 16-bit limbs of $a$ with $h_0$ being the least significant limb. Thus, stack transition for this operation must satisfy the following constraints: + +$$ +s_{0} = 2^{48} \cdot h_3 + 2^{32} \cdot h_2 + 2^{16} \cdot h_1 + h_0 \text{ | degree} = 1 +$$ + +$$ +s_{1}' = 2^{16} \cdot h_1 + h_0 \text{ | degree} = 1 +$$ + +$$ +s_{0}' = 2^{16} \cdot h_3 + h_2 \text{ | degree} = 1 +$$ + +In addition to the above constraints, we also need to verify that values in $h_0, ..., h_3$ are smaller than $2^{16}$, which we can do using 16-bit range checks as described [previously](#range-checks). Also, we need to make sure that values in $h_0, ..., h_3$, when combined, form a valid field element, which we can do by putting a nondeterministic value $m$ into helper register $h_4$ and using the technique described [here](#checking-element-validity). + +The effect of this operation on the rest of the stack is: +* **Right shift** starting from position $1$. + +## U32ASSERT2 +Assume $a$ and $b$ are the elements at the top of the stack. The `U32ASSERT2` verifies that both $a$ and $b$ are smaller than $2^{32}$. The diagram below illustrates this graphically. + +![u32assert2](../../img/design/stack/u32_operations/U32ASSERT2.png) + +To facilitate this operation, the prover sets values in $h_0$ and $h_1$ to low and high 16-bit limbs of $a$, and values in $h_2$ and $h_3$ to to low and high 16-bit limbs of $b$. Thus, stack transition for this operation must satisfy the following constraints: + +$$ +s_0' = 2^{16} \cdot h_3 + h_2 \text{ | degree} = 1 +$$ + +$$ +s_1' = 2^{16} \cdot h_1 + h_0 \text{ | degree} = 1 +$$ + +In addition to the above constraints, we also need to verify that values in $h_0, ..., h_3$ are smaller than $2^{16}$, which we can do using 16-bit range checks as described [previously](#range-checks). + +The effect of this operation on the rest of the stack is: +* **No change** starting from position $0$ - i.e., the state of the stack does not change. + +## U32ADD +Assume $a$ and $b$ are the values at the top of the stack which are known to be smaller than $2^{32}$. The `U32ADD` operation computes $(c,d) \leftarrow a + b$, where $c$ contains the low 32-bits of the result, and $d$ is the carry bit. The diagram below illustrates this graphically. + +![u32add](../../img/design/stack/u32_operations/U32ADD.png) + +To facilitate this operation, the prover sets values in $h_0$, $h_1$, and $h_2$ to 16-bit limbs of $a+b$ with $h_0$ being the least significant limb. Value in $h_3$ is set to $0$. Thus, stack transition for this operation must satisfy the following constraints: + +$$ +s_0 + s_1 = 2^{32} \cdot h_2 + 2^{16} \cdot h_1 + h_0 \text{ | degree} = 1 +$$ + +$$ +s_0' = h_2 \text{ | degree} = 1 +$$ + +$$ +s_1' = 2^{16} \cdot h_1 + h_0 \text{ | degree} = 1 +$$ + +In addition to the above constraints, we also need to verify that values in $h_0, ..., h_3$ are smaller than $2^{16}$, which we can do using 16-bit range checks as described [previously](#range-checks). + +The effect of this operation on the rest of the stack is: +* **No change** starting from position $2$. + +## U32ADD3 +Assume $a$, $b$, $c$ are the values at the top of the stack which are known to be smaller than $2^{32}$. The `U32ADD3` operation computes $(d, e) \leftarrow a + b + c$, where $c$ and $d$ contains the low and the high 32-bits of the result respectively. The diagram below illustrates this graphically. + +![u32add3](../../img/design/stack/u32_operations/U32ADD3.png) + +To facilitate this operation, the prover sets values in $h_0$, $h_1$, and $h_2$ to 16-bit limbs of $a+b+c$ with $h_0$ being the least significant limb. Value in $h_3$ is set to $0$. Thus, stack transition for this operation must satisfy the following constraints: + +$$ +s_0 + s_1 + s_2 = 2^{32} \cdot h_2 + 2^{16} \cdot h_1 + h_0 \text{ | degree} = 1 +$$ + +$$ +s_0' = h_2 \text{ | degree} = 1 +$$ + +$$ +s_1' = 2^{16} \cdot h_1 + h_0 \text{ | degree} = 1 +$$ + +In addition to the above constraints, we also need to verify that values in $h_0, ..., h_3$ are smaller than $2^{16}$, which we can do using 16-bit range checks as described [previously](#range-checks). + +The effect of this operation on the rest of the stack is: +* **Left shift** starting from position $3$. + +## U32SUB +Assume $a$ and $b$ are the values at the top of the stack which are known to be smaller than $2^{32}$. The `U32SUB` operation computes $(c, d) \leftarrow a - b$, where $c$ contains the 32-bit result in two's complement, and $d$ is the borrow bit. The diagram below illustrates this graphically. + +![u32sub](../../img/design/stack/u32_operations/U32SUB.png) + +To facilitate this operation, the prover sets values in $h_0$ and $h_1$ to the low and the high 16-bit limbs of $a-b$ respectively. Values in $h_2$ and $h_3$ are set to $0$. Thus, stack transition for this operation must satisfy the following constraints: + +$$ +s_1 = s_0 + s_1' + 2^{32} \cdot s_0' \text{ | degree} = 1 +$$ + +$$ +s_0'^2 - s_0' = 0 \text{ | degree} = 2 +$$ + +$$ +s_1' = 2^{16} \cdot h_1 + h_0 \text{ | degree} = 1 +$$ + +In addition to the above constraints, we also need to verify that values in $h_0, ..., h_3$ are smaller than $2^{16}$, which we can do using 16-bit range checks as described [previously](#range-checks). + +The effect of this operation on the rest of the stack is: +* **No change** starting from position $2$. + +## U32MUL +Assume $a$ and $b$ are the values at the top of the stack which are known to be smaller than $2^{32}$. The `U32MUL` operation computes $(c, d) \leftarrow a \cdot b$, where $c$ and $d$ contain the low and the high 32-bits of the result respectively. The diagram below illustrates this graphically. + +![u32mul](../../img/design/stack/u32_operations/U32MUL.png) + +To facilitate this operation, the prover sets values in $h_0, ..., h_3$ to 16-bit limbs of $a \cdot b$ with $h_0$ being the least significant limb. Thus, stack transition for this operation must satisfy the following constraints: + +$$ +s_0 \cdot s_1 = 2^{48} \cdot h_3 + 2^{32} \cdot h_2 + 2^{16} \cdot h_1 + h_0 \text{ | degree} = 2 +$$ + +$$ +s_1' = 2^{16} \cdot h_1 + h_0 \text{ | degree} = 1 +$$ + +$$ +s_0' = 2^{16} \cdot h_3 + h_2 \text{ | degree} = 1 +$$ + +In addition to the above constraints, we also need to verify that values in $h_0, ..., h_3$ are smaller than $2^{16}$, which we can do using 16-bit range checks as described [previously](#range-checks). Also, we need to make sure that values in $h_0, ..., h_3$, when combined, form a valid field element, which we can do by putting a nondeterministic value $m$ into helper register $h_4$ and using the technique described [here](#checking-element-validity). + +The effect of this operation on the rest of the stack is: +* **No change** starting from position $2$. + +## U32MADD +Assume $a$, $b$, $c$ are the values at the top of the stack which are known to be smaller than $2^{32}$. The `U32MADD` operation computes $(d, e) \leftarrow a +b \cdot c$, where $c$ and $d$ contains the low and the high 32-bits of $a + b \cdot c$. The diagram below illustrates this graphically. + +![u32madd](../../img/design/stack/u32_operations/U32MADD.png) + +To facilitate this operation, the prover sets values in $h_0, ..., h_3$ to 16-bit limbs of $a + b \cdot c$ with $h_0$ being the least significant limb. Thus, stack transition for this operation must satisfy the following constraints: + +$$ +s_0 \cdot s_1 + s_2 = 2^{48} \cdot h_3 + 2^{32} \cdot h_2 + 2^{16} \cdot h_1 + h_0 \text{ | degree} = 2 +$$ + +$$ +s_1' = 2^{16} \cdot h_1 + h_0 \text{ | degree} = 1 +$$ + +$$ +s_0' = 2^{16} \cdot h_3 + h_2 \text{ | degree} = 1 +$$ + +In addition to the above constraints, we also need to verify that values in $h_0, ..., h_3$ are smaller than $2^{16}$, which we can do using 16-bit range checks as described [previously](#range-checks). Also, we need to make sure that values in $h_0, ..., h_3$, when combined, form a valid field element, which we can do by putting a nondeterministic value $m$ into helper register $h_4$ and using the technique described [here](#checking-element-validity). + +**Note**: that the above constraints guarantee the correctness of the operation iff $a + b \cdot c$ cannot overflow field modules (which is the case for the field with modulus $2^{64} - 2^{32} + 1$). + +The effect of this operation on the rest of the stack is: +* **Left shift** starting from position $3$. + +## U32DIV +Assume $a$ and $b$ are the values at the top of the stack which are known to be smaller than $2^{32}$. The `U32DIV` operation computes $(c, d) \leftarrow a / b$, where $c$ contains the quotient and $d$ contains the remainder. The diagram below illustrates this graphically. + +![u32div](../../img/design/stack/u32_operations/U32DIV.png) + +To facilitate this operation, the prover sets values in $h_0$ and $h_1$ to 16-bit limbs of $a - c$, and values in $h_2$ and $h_3$ to 16-bit limbs of $b - d - 1$. Thus, stack transition for this operation must satisfy the following constraints: + +$$ +s_1 = s_0 \cdot s_1' + s_0' \text{ | degree} = 2 +$$ + +$$ +s_1 - s_1' = 2^{16} \cdot h_1 + h_0 \text{ | degree} = 1 +$$ + +$$ +s_0 - s_0' - 1= 2^{16} \cdot h_2 + h_3 \text{ | degree} = 1 +$$ + +The second constraint enforces that $s_1' \leq s_1$, while the third constraint enforces that $s_0' < s_0$. + +The effect of this operation on the rest of the stack is: +* **No change** starting from position $2$. + +## U32AND +Assume $a$ and $b$ are the values at the top of the stack. The `U32AND` operation computes $c \leftarrow (a \land b)$, where $c$ is the result of performing a bitwise AND on $a$ and $b$. The diagram below illustrates this graphically. + +![u32and](../../img/design/stack/u32_operations/U32AND.png) + +To facilitate this operation, we will need to make a request to the chiplet bus $b_{chip}$ by dividing its current value by the value representing bitwise operation request. This can be enforced with the following constraint: + +$$ +b_{chip}' \cdot \left(\alpha_0 + \alpha_1 \cdot op_{u32and} + \alpha_2 \cdot s_0 + \alpha_3 \cdot s_1 + \alpha_4 \cdot s_0' \right) = b_{chip} \text{ | degree} = 2 +$$ + +In the above, $op_{u32and}$ is the unique [operation label](../chiplets/index.md#operation-labels) of the bitwise `AND` operation. + +**Note**: unlike for many other u32 operations, bitwise AND operation does not assume that the values at the top of the stack are smaller than $2^{32}$. This is because the lookup will fail for any inputs which are not 32-bit integers. + +The effect of this operation on the rest of the stack is: +* **Left shift** starting from position $2$. + +## U32XOR +Assume $a$ and $b$ are the values at the top of the stack. The `U32XOR` operation computes $c \leftarrow (a \oplus b)$, where $c$ is the result of performing a bitwise XOR on $a$ and $b$. The diagram below illustrates this graphically. + +![u32xor](../../img/design/stack/u32_operations/U32XOR.png) + +To facilitate this operation, we will need to make a request to the chiplet bus $b_{chip}$ by dividing its current value by the value representing bitwise operation request. This can be enforced with the following constraint: + +> $$ +> b_{chip}' \cdot \left(\alpha_0 + \alpha_1 \cdot op_{u32xor} + \alpha_2 \cdot s_0 + \alpha_3 \cdot s_1 + \alpha_4 \cdot s_0' \right) = b_{chip} \text{ | degree} = 2 +> $$ + +In the above, $op_{u32xor}$ is the unique [operation label](../chiplets/index.md#operation-labels) of the bitwise `XOR` operation. + +**Note**: unlike for many other u32 operations, bitwise XOR operation does not assume that the values at the top of the stack are smaller than $2^{32}$. This is because the lookup will fail for any inputs which are not 32-bit integers. + +The effect of this operation on the rest of the stack is: +* **Left shift** starting from position $2$. diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/chiplets/bitwise/bitwise_execution_trace.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/chiplets/bitwise/bitwise_execution_trace.png new file mode 100644 index 0000000..a554cff Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/chiplets/bitwise/bitwise_execution_trace.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/chiplets/chiplets.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/chiplets/chiplets.png new file mode 100644 index 0000000..fa0ebae Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/chiplets/chiplets.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/chiplets/hasher.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/chiplets/hasher.png new file mode 100644 index 0000000..32f7e73 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/chiplets/hasher.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/chiplets/hasher/hash_1_permutation_trace.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/chiplets/hasher/hash_1_permutation_trace.png new file mode 100644 index 0000000..6e1f9fa Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/chiplets/hasher/hash_1_permutation_trace.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/chiplets/hasher/hash_2_to_1_hash.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/chiplets/hasher/hash_2_to_1_hash.png new file mode 100644 index 0000000..5244853 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/chiplets/hasher/hash_2_to_1_hash.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/chiplets/hasher/hash_execution_trace.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/chiplets/hasher/hash_execution_trace.png new file mode 100644 index 0000000..1f217c0 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/chiplets/hasher/hash_execution_trace.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/chiplets/hasher/hash_linear_hash_n.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/chiplets/hasher/hash_linear_hash_n.png new file mode 100644 index 0000000..9fae15d Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/chiplets/hasher/hash_linear_hash_n.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/chiplets/hasher/hash_merkle_tree.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/chiplets/hasher/hash_merkle_tree.png new file mode 100644 index 0000000..996c13f Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/chiplets/hasher/hash_merkle_tree.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/chiplets/hasher/hash_merkle_tree_trace.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/chiplets/hasher/hash_merkle_tree_trace.png new file mode 100644 index 0000000..3cccacd Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/chiplets/hasher/hash_merkle_tree_trace.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/chiplets/hasher_bitwise.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/chiplets/hasher_bitwise.png new file mode 100644 index 0000000..9bcaa28 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/chiplets/hasher_bitwise.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/chiplets/memory/memory_alternative_design.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/chiplets/memory/memory_alternative_design.png new file mode 100644 index 0000000..2c24b0d Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/chiplets/memory/memory_alternative_design.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/chiplets/memory/memory_context_separation.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/chiplets/memory/memory_context_separation.png new file mode 100644 index 0000000..f6fe123 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/chiplets/memory/memory_context_separation.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/chiplets/memory/memory_limitation_diagram.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/chiplets/memory/memory_limitation_diagram.png new file mode 100644 index 0000000..acc1c90 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/chiplets/memory/memory_limitation_diagram.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/chiplets/memory/memory_miden_vm_layout.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/chiplets/memory/memory_miden_vm_layout.png new file mode 100644 index 0000000..5ddfa01 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/chiplets/memory/memory_miden_vm_layout.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/chiplets/memory/memory_non_contiguous_memory.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/chiplets/memory/memory_non_contiguous_memory.png new file mode 100644 index 0000000..c17bf0d Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/chiplets/memory/memory_non_contiguous_memory.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/chiplets/memory/memory_read_write.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/chiplets/memory/memory_read_write.png new file mode 100644 index 0000000..828065a Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/chiplets/memory/memory_read_write.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/chiplets/memory/memory_reading_memory.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/chiplets/memory/memory_reading_memory.png new file mode 100644 index 0000000..6dc1057 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/chiplets/memory/memory_reading_memory.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/chiplets/memory/memory_writing_to_memory.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/chiplets/memory/memory_writing_to_memory.png new file mode 100644 index 0000000..3ef5bf1 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/chiplets/memory/memory_writing_to_memory.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/block_hash_table.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/block_hash_table.png new file mode 100644 index 0000000..91713e7 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/block_hash_table.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/constraints/air_decoder_columns.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/constraints/air_decoder_columns.png new file mode 100644 index 0000000..3db2751 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/constraints/air_decoder_columns.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/constraints/air_decoder_in_spans_column_constraint.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/constraints/air_decoder_in_spans_column_constraint.png new file mode 100644 index 0000000..e675de2 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/constraints/air_decoder_in_spans_column_constraint.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/constraints/air_decoder_left_right_child.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/constraints/air_decoder_left_right_child.png new file mode 100644 index 0000000..81cbb8d Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/constraints/air_decoder_left_right_child.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/constraints/air_decoder_op_group_constraint.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/constraints/air_decoder_op_group_constraint.png new file mode 100644 index 0000000..e52d55d Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/constraints/air_decoder_op_group_constraint.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_OPERATION_batch_flags.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_OPERATION_batch_flags.png new file mode 100644 index 0000000..20cb5f9 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_OPERATION_batch_flags.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_block_stack_table.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_block_stack_table.png new file mode 100644 index 0000000..b90826c Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_block_stack_table.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_call_operation.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_call_operation.png new file mode 100644 index 0000000..625a3dd Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_call_operation.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_decoding_span_block_with_push.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_decoding_span_block_with_push.png new file mode 100644 index 0000000..e90eed1 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_decoding_span_block_with_push.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_dyn_block_decoding.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_dyn_block_decoding.png new file mode 100644 index 0000000..955d88b Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_dyn_block_decoding.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_dyn_operation.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_dyn_operation.png new file mode 100644 index 0000000..fec8d15 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_dyn_operation.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_dyncall_operation.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_dyncall_operation.png new file mode 100644 index 0000000..b40697d Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_dyncall_operation.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_end_operation.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_end_operation.png new file mode 100644 index 0000000..2dd02bc Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_end_operation.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_halt_operation.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_halt_operation.png new file mode 100644 index 0000000..3bb9d56 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_halt_operation.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_imm_vale_op_group_table.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_imm_vale_op_group_table.png new file mode 100644 index 0000000..eb659bf Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_imm_vale_op_group_table.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_join_block_decoding.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_join_block_decoding.png new file mode 100644 index 0000000..2e3a437 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_join_block_decoding.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_join_operation.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_join_operation.png new file mode 100644 index 0000000..a2372e4 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_join_operation.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_loop_execution.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_loop_execution.png new file mode 100644 index 0000000..23e010e Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_loop_execution.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_loop_operation.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_loop_operation.png new file mode 100644 index 0000000..fc9dcb4 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_loop_operation.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_loop_skipping.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_loop_skipping.png new file mode 100644 index 0000000..4ae5bdc Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_loop_skipping.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_multi_batch_span.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_multi_batch_span.png new file mode 100644 index 0000000..3111023 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_multi_batch_span.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_op_group_table.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_op_group_table.png new file mode 100644 index 0000000..b814ec2 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_op_group_table.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_op_group_table_after_span_op.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_op_group_table_after_span_op.png new file mode 100644 index 0000000..4efed18 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_op_group_table_after_span_op.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_op_group_table_multi_span.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_op_group_table_multi_span.png new file mode 100644 index 0000000..1fdf289 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_op_group_table_multi_span.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_op_group_table_post_respan.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_op_group_table_post_respan.png new file mode 100644 index 0000000..d3d0d86 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_op_group_table_post_respan.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_operation_group_decoding.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_operation_group_decoding.png new file mode 100644 index 0000000..e206f58 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_operation_group_decoding.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_repeat_operation.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_repeat_operation.png new file mode 100644 index 0000000..30194a0 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_repeat_operation.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_respan_operation.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_respan_operation.png new file mode 100644 index 0000000..899fec5 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_respan_operation.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_single_batch_span.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_single_batch_span.png new file mode 100644 index 0000000..20c3fe0 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_single_batch_span.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_span_block.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_span_block.png new file mode 100644 index 0000000..c36f719 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_span_block.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_split_block_decoding.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_split_block_decoding.png new file mode 100644 index 0000000..8503fe9 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_split_block_decoding.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_split_operation.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_split_operation.png new file mode 100644 index 0000000..265d822 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_split_operation.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_state_block_hash_2.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_state_block_hash_2.png new file mode 100644 index 0000000..eb5290a Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_state_block_hash_2.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_state_block_hash_4.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_state_block_hash_4.png new file mode 100644 index 0000000..1a0a8b1 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_state_block_hash_4.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_state_block_hash_6.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_state_block_hash_6.png new file mode 100644 index 0000000..8da273f Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_state_block_hash_6.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_syscall_operation.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_syscall_operation.png new file mode 100644 index 0000000..6371910 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_syscall_operation.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_trace.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_trace.png new file mode 100644 index 0000000..3db2751 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/decoder/decoder_trace.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/lookups/logup_component.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/lookups/logup_component.png new file mode 100644 index 0000000..364e7cb Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/lookups/logup_component.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/lookups/logup_table.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/lookups/logup_table.png new file mode 100644 index 0000000..ef8f142 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/lookups/logup_table.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/programs/call_block.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/programs/call_block.png new file mode 100644 index 0000000..2f65c50 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/programs/call_block.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/programs/dyn_block.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/programs/dyn_block.png new file mode 100644 index 0000000..ff20581 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/programs/dyn_block.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/programs/join_block.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/programs/join_block.png new file mode 100644 index 0000000..9be9bae Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/programs/join_block.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/programs/loop_block.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/programs/loop_block.png new file mode 100644 index 0000000..87e0978 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/programs/loop_block.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/programs/mast_of_program.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/programs/mast_of_program.png new file mode 100644 index 0000000..9940676 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/programs/mast_of_program.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/programs/span_block_creation.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/programs/span_block_creation.png new file mode 100644 index 0000000..acf04c3 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/programs/span_block_creation.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/programs/split_block.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/programs/split_block.png new file mode 100644 index 0000000..33f1da2 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/programs/split_block.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/programs/syscall_block.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/programs/syscall_block.png new file mode 100644 index 0000000..abd1b33 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/programs/syscall_block.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/range/rc_16_bit_logup.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/range/rc_16_bit_logup.png new file mode 100644 index 0000000..79dadaf Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/range/rc_16_bit_logup.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/range/rc_16_bit_range_check.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/range/rc_16_bit_range_check.png new file mode 100644 index 0000000..9e705ba Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/range/rc_16_bit_range_check.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/range/rc_8_bit_logup.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/range/rc_8_bit_logup.png new file mode 100644 index 0000000..b7cc4db Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/range/rc_8_bit_logup.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/range/rc_8_bit_range_check.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/range/rc_8_bit_range_check.png new file mode 100644 index 0000000..bcedb9c Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/range/rc_8_bit_range_check.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/range/rc_better_construction.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/range/rc_better_construction.png new file mode 100644 index 0000000..3f0e66b Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/range/rc_better_construction.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/range/rc_table_post_8_bit_range_check.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/range/rc_table_post_8_bit_range_check.png new file mode 100644 index 0000000..2032e10 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/range/rc_table_post_8_bit_range_check.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/range/rc_with_bridge_rows.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/range/rc_with_bridge_rows.png new file mode 100644 index 0000000..df95f5f Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/range/rc_with_bridge_rows.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/range/rc_with_bridge_rows.zip b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/range/rc_with_bridge_rows.zip new file mode 100644 index 0000000..8b4abde Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/range/rc_with_bridge_rows.zip differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/crypto_ops/EVALCIRCUIT.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/crypto_ops/EVALCIRCUIT.png new file mode 100644 index 0000000..4fc7b09 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/crypto_ops/EVALCIRCUIT.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/crypto_ops/FRIE2F4.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/crypto_ops/FRIE2F4.png new file mode 100644 index 0000000..0188880 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/crypto_ops/FRIE2F4.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/crypto_ops/HORNERBASE.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/crypto_ops/HORNERBASE.png new file mode 100644 index 0000000..2779d15 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/crypto_ops/HORNERBASE.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/crypto_ops/HORNEREXT.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/crypto_ops/HORNEREXT.png new file mode 100644 index 0000000..80f69d0 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/crypto_ops/HORNEREXT.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/crypto_ops/HPERM.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/crypto_ops/HPERM.png new file mode 100644 index 0000000..f6aabc8 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/crypto_ops/HPERM.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/crypto_ops/MPVERIFY.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/crypto_ops/MPVERIFY.png new file mode 100644 index 0000000..5a642d1 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/crypto_ops/MPVERIFY.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/crypto_ops/MRUPDATE.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/crypto_ops/MRUPDATE.png new file mode 100644 index 0000000..19451d2 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/crypto_ops/MRUPDATE.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/decorator_operations/DIVRESULTU64.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/decorator_operations/DIVRESULTU64.png new file mode 100644 index 0000000..408e574 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/decorator_operations/DIVRESULTU64.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/decorator_operations/MERKLENODE.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/decorator_operations/MERKLENODE.png new file mode 100644 index 0000000..68b8339 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/decorator_operations/MERKLENODE.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/field_operations/ADD.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/field_operations/ADD.png new file mode 100644 index 0000000..1cb8fbb Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/field_operations/ADD.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/field_operations/AND.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/field_operations/AND.png new file mode 100644 index 0000000..c4e4ff6 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/field_operations/AND.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/field_operations/EQ.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/field_operations/EQ.png new file mode 100644 index 0000000..066b894 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/field_operations/EQ.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/field_operations/EQZ.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/field_operations/EQZ.png new file mode 100644 index 0000000..34055dd Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/field_operations/EQZ.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/field_operations/EXPACC.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/field_operations/EXPACC.png new file mode 100644 index 0000000..3bf2dd3 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/field_operations/EXPACC.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/field_operations/EXT2MUL.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/field_operations/EXT2MUL.png new file mode 100644 index 0000000..51a369e Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/field_operations/EXT2MUL.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/field_operations/INCR.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/field_operations/INCR.png new file mode 100644 index 0000000..579e325 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/field_operations/INCR.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/field_operations/INV.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/field_operations/INV.png new file mode 100644 index 0000000..66085bc Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/field_operations/INV.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/field_operations/MUL.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/field_operations/MUL.png new file mode 100644 index 0000000..5811b1e Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/field_operations/MUL.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/field_operations/NEG.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/field_operations/NEG.png new file mode 100644 index 0000000..afcd31b Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/field_operations/NEG.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/field_operations/NOT.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/field_operations/NOT.png new file mode 100644 index 0000000..9258b81 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/field_operations/NOT.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/field_operations/OR.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/field_operations/OR.png new file mode 100644 index 0000000..9562532 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/field_operations/OR.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/io_ops/ADVPOP.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/io_ops/ADVPOP.png new file mode 100644 index 0000000..bbbf4de Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/io_ops/ADVPOP.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/io_ops/ADVPOPW.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/io_ops/ADVPOPW.png new file mode 100644 index 0000000..fe1d632 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/io_ops/ADVPOPW.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/io_ops/MLOAD.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/io_ops/MLOAD.png new file mode 100644 index 0000000..a770874 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/io_ops/MLOAD.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/io_ops/MLOADW.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/io_ops/MLOADW.png new file mode 100644 index 0000000..9354925 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/io_ops/MLOADW.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/io_ops/MSTORE.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/io_ops/MSTORE.png new file mode 100644 index 0000000..d607870 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/io_ops/MSTORE.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/io_ops/MSTOREW.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/io_ops/MSTOREW.png new file mode 100644 index 0000000..085c6cc Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/io_ops/MSTOREW.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/io_ops/MSTREAM.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/io_ops/MSTREAM.png new file mode 100644 index 0000000..6fb82bd Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/io_ops/MSTREAM.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/io_ops/SDEPTH.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/io_ops/SDEPTH.png new file mode 100644 index 0000000..ce5ca60 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/io_ops/SDEPTH.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/overflow_table_layout.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/overflow_table_layout.png new file mode 100644 index 0000000..bcecf65 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/overflow_table_layout.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/stack_1st_left_shift.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/stack_1st_left_shift.png new file mode 100644 index 0000000..2061cc6 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/stack_1st_left_shift.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/stack_ops/CSWAP.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/stack_ops/CSWAP.png new file mode 100644 index 0000000..55f3104 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/stack_ops/CSWAP.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/stack_ops/CSWAPW.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/stack_ops/CSWAPW.png new file mode 100644 index 0000000..88dbbe3 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/stack_ops/CSWAPW.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/stack_ops/DROP.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/stack_ops/DROP.png new file mode 100644 index 0000000..49887e3 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/stack_ops/DROP.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/stack_ops/DUP(n).png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/stack_ops/DUP(n).png new file mode 100644 index 0000000..fedf566 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/stack_ops/DUP(n).png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/stack_ops/MOVDN(n).png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/stack_ops/MOVDN(n).png new file mode 100644 index 0000000..52b032a Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/stack_ops/MOVDN(n).png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/stack_ops/MOVUP(n).png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/stack_ops/MOVUP(n).png new file mode 100644 index 0000000..5d0870c Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/stack_ops/MOVUP(n).png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/stack_ops/PAD.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/stack_ops/PAD.png new file mode 100644 index 0000000..a94ff69 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/stack_ops/PAD.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/stack_ops/SWAP.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/stack_ops/SWAP.png new file mode 100644 index 0000000..5f1dc91 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/stack_ops/SWAP.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/stack_ops/SWAPDW.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/stack_ops/SWAPDW.png new file mode 100644 index 0000000..18fa868 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/stack_ops/SWAPDW.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/stack_ops/SWAPW.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/stack_ops/SWAPW.png new file mode 100644 index 0000000..8f67726 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/stack_ops/SWAPW.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/stack_ops/SWAPW2.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/stack_ops/SWAPW2.png new file mode 100644 index 0000000..d2d23a2 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/stack_ops/SWAPW2.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/stack_ops/SWAPW3.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/stack_ops/SWAPW3.png new file mode 100644 index 0000000..03f8516 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/stack_ops/SWAPW3.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/stack_overflow_push_2nd_item.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/stack_overflow_push_2nd_item.png new file mode 100644 index 0000000..c0d7ff1 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/stack_overflow_push_2nd_item.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/stack_overflow_table_post_1_right_shift.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/stack_overflow_table_post_1_right_shift.png new file mode 100644 index 0000000..de5b8e1 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/stack_overflow_table_post_1_right_shift.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/stack_overflow_table_post_2_right_shift.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/stack_overflow_table_post_2_right_shift.png new file mode 100644 index 0000000..4fe5824 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/stack_overflow_table_post_2_right_shift.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/stack_right_shift.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/stack_right_shift.png new file mode 100644 index 0000000..3b0060b Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/stack_right_shift.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/system_ops/ASSERT.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/system_ops/ASSERT.png new file mode 100644 index 0000000..407a755 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/system_ops/ASSERT.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/system_ops/CLK.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/system_ops/CLK.png new file mode 100644 index 0000000..ad00903 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/system_ops/CLK.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/trace_layout.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/trace_layout.png new file mode 100644 index 0000000..d12d8fe Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/trace_layout.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/u32_operations/U32ADD.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/u32_operations/U32ADD.png new file mode 100644 index 0000000..30bbf89 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/u32_operations/U32ADD.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/u32_operations/U32ADD3.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/u32_operations/U32ADD3.png new file mode 100644 index 0000000..9d1e0ac Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/u32_operations/U32ADD3.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/u32_operations/U32AND.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/u32_operations/U32AND.png new file mode 100644 index 0000000..3d916e2 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/u32_operations/U32AND.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/u32_operations/U32ASSERT2.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/u32_operations/U32ASSERT2.png new file mode 100644 index 0000000..c32a18b Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/u32_operations/U32ASSERT2.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/u32_operations/U32DIV.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/u32_operations/U32DIV.png new file mode 100644 index 0000000..e71a6da Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/u32_operations/U32DIV.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/u32_operations/U32MADD.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/u32_operations/U32MADD.png new file mode 100644 index 0000000..4ee5c26 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/u32_operations/U32MADD.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/u32_operations/U32MUL.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/u32_operations/U32MUL.png new file mode 100644 index 0000000..b1ca4ce Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/u32_operations/U32MUL.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/u32_operations/U32SPLIT.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/u32_operations/U32SPLIT.png new file mode 100644 index 0000000..9c9c6e8 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/u32_operations/U32SPLIT.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/u32_operations/U32SUB.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/u32_operations/U32SUB.png new file mode 100644 index 0000000..ed7bdb7 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/u32_operations/U32SUB.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/u32_operations/U32XOR.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/u32_operations/U32XOR.png new file mode 100644 index 0000000..1dc6524 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/stack/u32_operations/U32XOR.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/design/vm_trace.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/vm_trace.png new file mode 100644 index 0000000..62788bc Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/design/vm_trace.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/intro/vm_components.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/intro/vm_components.png new file mode 100644 index 0000000..109cb8d Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/intro/vm_components.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/user_docs/assembly/assembly_to_VM.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/user_docs/assembly/assembly_to_VM.png new file mode 100644 index 0000000..2e39504 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/user_docs/assembly/assembly_to_VM.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/user_docs/assembly/execution_contexts/context_transitions.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/user_docs/assembly/execution_contexts/context_transitions.png new file mode 100644 index 0000000..2dd3bb4 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/user_docs/assembly/execution_contexts/context_transitions.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/user_docs/assembly/execution_contexts/mem_layout.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/user_docs/assembly/execution_contexts/mem_layout.png new file mode 100644 index 0000000..70588dd Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/user_docs/assembly/execution_contexts/mem_layout.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/img/user_docs/assembly/overview/miden_vm_overview.png b/versioned_docs/version-0.12 (stable)/miden-vm/img/user_docs/assembly/overview/miden_vm_overview.png new file mode 100644 index 0000000..8572b45 Binary files /dev/null and b/versioned_docs/version-0.12 (stable)/miden-vm/img/user_docs/assembly/overview/miden_vm_overview.png differ diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/index.md b/versioned_docs/version-0.12 (stable)/miden-vm/index.md new file mode 100644 index 0000000..f642ad6 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-vm/index.md @@ -0,0 +1,43 @@ +--- +title: "Introduction" +sidebar_position: 1 +--- + +# Introduction +Miden VM is a zero-knowledge virtual machine written in Rust. For any program executed on Miden VM, a STARK-based proof of execution is automatically generated. This proof can then be used by anyone to verify that the program was executed correctly without the need for re-executing the program or even knowing the contents of the program. + +## Status and features +Miden VM is currently on release v0.19. In this release, most of the core features of the VM have been stabilized, and most of the STARK proof generation has been implemented. While we expect to keep making changes to the VM internals, the external interfaces should remain relatively stable, and we will do our best to minimize the amount of breaking changes going forward. + +At this point, Miden VM is good enough for experimentation, and even for real-world applications, but it is not yet ready for production use. The codebase has not been audited and contains known and unknown bugs and security flaws. + +### Feature highlights +Miden VM is a fully-featured virtual machine. Despite being optimized for zero-knowledge proof generation, it provides all the features one would expect from a regular VM. To highlight a few: + +* **Flow control.** Miden VM is Turing-complete and supports familiar flow control structures such as conditional statements and counter/condition-controlled loops. There are no restrictions on the maximum number of loop iterations or the depth of control flow logic. +* **Procedures.** Miden assembly programs can be broken into subroutines called *procedures*. This improves code modularity and helps reduce the size of Miden VM programs. +* **Execution contexts.** Miden VM program execution can span multiple isolated contexts, each with its own dedicated memory space. The contexts are separated into the *root context* and *user contexts*. The root context can be accessed from user contexts via customizable kernel calls. +* **Memory.** Miden VM supports read-write random-access memory. Procedures can reserve portions of global memory for easier management of local variables. +* **u32 operations.** Miden VM supports native operations with 32-bit unsigned integers. This includes basic arithmetic, comparison, and bitwise operations. +* **Cryptographic operations.** Miden assembly provides built-in instructions for computing hashes and verifying Merkle paths. These instructions use Rescue Prime Optimized hash function (which is the native hash function of the VM). +* **External libraries.** Miden VM supports compiling programs against pre-defined libraries. The VM ships with one such library: Miden `stdlib` which adds support for such things as 64-bit unsigned integers. Developers can build other similar libraries to extend the VM's functionality in ways which fit their use cases. +* **Nondeterminism**. Unlike traditional virtual machines, Miden VM supports nondeterministic programming. This means a prover may do additional work outside of the VM and then provide execution *hints* to the VM. These hints can be used to dramatically speed up certain types of computations, as well as to supply secret inputs to the VM. +* **Customizable hosts.** Miden VM can be instantiated with user-defined hosts. These hosts are used to supply external data to the VM during execution/proof generation (via nondeterministic inputs) and can connect the VM to arbitrary data sources (e.g., a database or RPC calls). + +### Planned features +In the coming months we plan to finalize the design of the VM and implement support for the following features: + +* **Recursive proofs.** Miden VM will soon support efficient STARK-verifiers as pre-compiles, enabling infinitely recursive proofs and enabling proof composition in layers. This is an extremely useful tool for real-world applications that need to verify large numbers of proofs or build complex proof systems. +* **Better debugging.** Miden VM will provide a better debugging experience including the ability to place breakpoints, better source mapping, and more complete program analysis info. +* **Faulty execution.** Miden VM will support generating proofs for programs with faulty execution (a notoriously complex task in ZK context). That is, it will be possible to prove that execution of some program resulted in an error. + +## Structure of this document +This document is meant to provide an in-depth description of Miden VM. It is organized as follows: + +* In the introduction, we provide a high-level overview of Miden VM and describe how to run simple programs. +* In the user documentation section, we provide developer-focused documentation useful to those who want to develop on Miden VM or build compilers from higher-level languages to Miden assembly (the native language of Miden VM). +* In the design section, we provide in-depth descriptions of the VM's internals, including all AIR constraints for the proving system. We also provide the rationale for settling on specific design choices. +* Finally, in the background material section, we provide references to materials which could be useful for learning more about STARKs - the proving system behind Miden VM. + +## License +This project is dual-licensed under the [MIT](http://opensource.org/licenses/MIT) and [Apache 2.0](https://opensource.org/license/apache-2-0) licenses. diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/overview.md b/versioned_docs/version-0.12 (stable)/miden-vm/overview.md new file mode 100644 index 0000000..a915547 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-vm/overview.md @@ -0,0 +1,59 @@ +--- +title: "Overview" +sidebar_position: 2 +--- + +# Miden VM overview + +Miden VM is a stack machine. The base data type of the VM is a field element in a 64-bit [prime field](https://en.wikipedia.org/wiki/Finite_field) defined by modulus $p = 2^{64} - 2^{32} + 1$. This means that all values that the VM operates with are field elements in this field (i.e., values between $0$ and $2^{64} - 2^{32}$, both inclusive). + +Miden VM consists of four high-level components as illustrated below. + +![vm_components](./img/intro/vm_components.png) + +These components are: + +- **Stack** which is a push-down stack where each item is a field element. Most assembly instructions operate with values located on the stack. The stack can grow up to $2^{32}$ items deep, however, only the top 16 items are directly accessible. +- **Memory** which is a linear random-access read-write memory. The memory is element-addressable, meaning, a single element is located at each address. However, there are instructions to read and write elements to/from memory both individually or in batches of four, since the latter is quite common. Memory addresses can be in the range $[0, 2^{32})$. +- **Chiplets** which are specialized circuits for accelerating certain types of computations. These include Rescue Prime Optimized (RPO) hash function, 32-bit binary operations, and 16-bit range checks. +- **Host** which is a way for the prover to communicate with the VM during runtime. This includes responding to the VM's requests for non-deterministic inputs and handling messages sent by the VM (e.g., for debugging purposes). The requests for non-deterministic inputs are handled by the host's _advice provider_. + +Miden VM comes with a default implementation of the host interface (with an in-memory advice provider). However, the users are able to provide their own implementations which can connect the VM to arbitrary data sources (e.g., a database or RPC calls) and define custom logic for handling events emitted by the VM. + +## Writing programs + +Our goal is to make Miden VM an easy compilation target for high-level languages such as Rust, Move, Sway, and others. We believe it is important to let people write programs in the languages of their choice. However, compilers to help with this have not been developed yet. Thus, for now, the primary way to write programs for Miden VM is to use [Miden assembly](./user_docs/assembly/index.md). + +While writing programs in assembly is far from ideal, Miden assembly does make this task a little bit easier by supporting high-level flow control structures and named procedures. + +## Inputs and outputs + +External inputs can be provided to Miden VM in two ways: + +1. Public inputs can be supplied to the VM by initializing the stack with desired values before a program starts executing. At most 16 values can be initialized in this way, so providing more than 16 values will cause an error. +2. Secret (or nondeterministic) inputs can be supplied to the VM via the [_advice provider_](#nondeterministic-inputs). There is no limit on how much data the advice provider can hold. + +After a program finishes executing, the elements remaining on the stack become the outputs of the program. Notice that having more than 16 values on the stack at the end of execution will cause an error, so the values beyond the top 16 elements of the stack should be dropped. We've provided the [`truncate_stack`](./user_docs/stdlib/sys.md) utility procedure in the standard library for this purpose. + +The number of public inputs and outputs of a program can be reduced by making use of the advice stack and Merkle trees. Just 4 elements are sufficient to represent a root of a Merkle tree, which can be expanded into an arbitrary number of values. + +For example, if we wanted to provide a thousand public input values to the VM, we could put these values into a Merkle tree, initialize the stack with the root of this tree, initialize the advice provider with the tree itself, and then retrieve values from the tree during program execution using `mtree_get` instruction (described [here](./user_docs/assembly/cryptographic_operations.md#hashing-and-merkle-trees)). + +### Stack depth restrictions + +For reasons explained [here](./design/stack/index.md), the VM imposes the restriction that the stack depth cannot be smaller than $16$. This has the following effects: + +- When initializing a program with fewer than $16$ inputs, the VM will pad the stack with zeros to ensure the depth is $16$ at the beginning of execution. +- If an operation would result in the stack depth dropping below $16$, the VM will insert a zero at the deep end of the stack to make sure the depth stays at $16$. + +### Nondeterministic inputs + +The _advice provider_ component is responsible for supplying nondeterministic inputs to the VM. These inputs only need to be known to the prover (i.e., they do not need to be shared with the verifier). + +The advice provider consists of three components: + +- **Advice stack** which is a one-dimensional array of field elements. Being a stack, the VM can either push new elements onto the advice stack, or pop the elements from its top. +- **Advice map** which is a key-value map where keys are words and values are vectors (dynamic arrays) of field elements. The VM can copy values from the advice map onto the advice stack as well as insert new values into the advice map (e.g., from a region of memory). +- **Merkle store** which contain structured data reducible to Merkle paths. Some examples of such structures are: Merkle tree, Sparse Merkle Tree, and a collection of Merkle paths. The VM can request Merkle paths from the Merkle store, as well as mutate it by updating or merging nodes contained in the store. + +The prover initializes the advice provider prior to executing a program, and from that point on the advice provider is manipulated solely by executing operations on the VM. **Security note:** When reading data from the advice provider, we can't assume it is valid - we always have to verify this (e.g., when writing and then reading the data, we need to make sure that the data we read hashes to some expected value). diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/performance.md b/versioned_docs/version-0.12 (stable)/miden-vm/performance.md new file mode 100644 index 0000000..9693a82 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-vm/performance.md @@ -0,0 +1,72 @@ +--- +title: "Performance" +sidebar_position: 4 +--- + +# Performance +The benchmarks below should be viewed only as a rough guide for expected future performance. The reasons for this are twofold: +1. Not all constraints have been implemented yet, and we expect that there will be some slowdown once constraint evaluation is completed. +2. Many optimizations have not been applied yet, and we expect that there will be some speedup once we dedicate some time to performance optimizations. + +Overall, we don't expect the benchmarks to change significantly, but there will definitely be some deviation from the below numbers in the future. + +A few general notes on performance: + +* Execution time is dominated by proof generation time. In fact, the time needed to run the program is usually under 1% of the time needed to generate the proof. +* Proof verification time is really fast. In most cases it is under 1 ms, but sometimes gets as high as 2 ms or 3 ms. +* Proof generation process is dynamically adjustable. In general, there is a trade-off between execution time, proof size, and security level (i.e. for a given security level, we can reduce proof size by increasing execution time, up to a point). +* Both proof generation and proof verification times are greatly influenced by the hash function used in the STARK protocol. In the benchmarks below, we use BLAKE3, which is a really fast hash function. + +## Single-core prover performance +When executed on a single CPU core, the current version of Miden VM operates at around 20 - 25 KHz. In the benchmarks below, the VM executes a Fibonacci calculator program on Apple M1 Pro CPU in a single thread. The generated proofs have a target security level of 96 bits. + +| VM cycles | Execution time | Proving time | RAM consumed | Proof size | +| :-------------: | :------------: | :----------: | :-----------: | :--------: | +| 210 | 1 ms | 60 ms | 20 MB | 46 KB | +| 212 | 2 ms | 180 ms | 52 MB | 56 KB | +| 214 | 8 ms | 680 ms | 240 MB | 65 KB | +| 216 | 28 ms | 2.7 sec | 950 MB | 75 KB | +| 218 | 81 ms | 11.4 sec | 3.7 GB | 87 KB | +| 220 | 310 ms | 47.5 sec | 14 GB | 100 KB | + +As can be seen from the above, proving time roughly doubles with every doubling in the number of cycles, but proof size grows much slower. + +We can also generate proofs at a higher security level. The cost of doing so is roughly doubling of proving time and roughly 40% increase in proof size. In the benchmarks below, the same Fibonacci calculator program was executed on Apple M1 Pro CPU at 128-bit target security level: + +| VM cycles | Execution time | Proving time | RAM consumed | Proof size | +| :-------------: | :------------: | :----------: | :-----------: | :--------: | +| 210 | 1 ms | 120 ms | 30 MB | 61 KB | +| 212 | 2 ms | 460 ms | 106 MB | 77 KB | +| 214 | 8 ms | 1.4 sec | 500 MB | 90 KB | +| 216 | 27 ms | 4.9 sec | 2.0 GB | 103 KB | +| 218 | 81 ms | 20.1 sec | 8.0 GB | 121 KB | +| 220 | 310 ms | 90.3 sec | 20.0 GB | 138 KB | + +## Multi-core prover performance +STARK proof generation is massively parallelizable. Thus, by taking advantage of multiple CPU cores we can dramatically reduce proof generation time. For example, when executed on an 8-core CPU (Apple M1 Pro), the current version of Miden VM operates at around 100 KHz. And when executed on a 64-core CPU (Amazon Graviton 3), the VM operates at around 250 KHz. + +In the benchmarks below, the VM executes the same Fibonacci calculator program for 220 cycles at 96-bit target security level: + +| Machine | Execution time | Proving time | Execution % | Implied Frequency | +| ------------------------------ | :------------: | :----------: | :---------: | :---------------: | +| Apple M1 Pro (16 threads) | 310 ms | 7.0 sec | 4.2% | 140 KHz | +| Apple M2 Max (16 threads) | 280 ms | 5.8 sec | 4.5% | 170 KHz | +| AMD Ryzen 9 5950X (16 threads) | 270 ms | 10.0 sec | 2.6% | 100 KHz | +| Amazon Graviton 3 (64 threads) | 330 ms | 3.6 sec | 8.5% | 265 KHz | + +### Recursive proofs +Proofs in the above benchmarks are generated using BLAKE3 hash function. While this hash function is very fast, it is not very efficient to execute in Miden VM. Thus, proofs generated using BLAKE3 are not well-suited for recursive proof verification. To support efficient recursive proofs, we need to use an arithmetization-friendly hash function. Miden VM natively supports Rescue Prime Optimized (RPO), which is one such hash function. One of the downsides of arithmetization-friendly hash functions is that they are considerably slower than regular hash functions. + +In the benchmarks below we execute the same Fibonacci calculator program for 220 cycles at 96-bit target security level using RPO hash function instead of BLAKE3: + +| Machine | Execution time | Proving time | Proving time (HW) | +| ------------------------------ | :------------: | :----------: | :---------------: | +| Apple M1 Pro (16 threads) | 310 ms | 94.3 sec | 42.0 sec | +| Apple M2 Max (16 threads) | 280 ms | 75.1 sec | 20.9 sec | +| AMD Ryzen 9 5950X (16 threads) | 270 ms | 59.3 sec | | +| Amazon Graviton 3 (64 threads) | 330 ms | 21.7 sec | 14.9 sec | + +In the above, proof generation on some platforms can be hardware-accelerated. Specifically: + +* On Apple M1/M2 platforms the built-in GPU is used for a part of proof generation process. +* On the Graviton platform, SVE vector extension is used to accelerate RPO computations. diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/theme/Admonition/Icon/Danger.tsx b/versioned_docs/version-0.12 (stable)/miden-vm/theme/Admonition/Icon/Danger.tsx new file mode 100644 index 0000000..ab14d7d --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-vm/theme/Admonition/Icon/Danger.tsx @@ -0,0 +1,19 @@ +import React, { type ReactNode } from "react"; +import type { Props } from "@theme/Admonition/Icon/Danger"; + +export default function AdmonitionIconDanger(props: Props): ReactNode { + return ( + + + + ); +} diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/theme/Admonition/Icon/Info.tsx b/versioned_docs/version-0.12 (stable)/miden-vm/theme/Admonition/Icon/Info.tsx new file mode 100644 index 0000000..59e48a5 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-vm/theme/Admonition/Icon/Info.tsx @@ -0,0 +1,20 @@ +import React, { type ReactNode } from "react"; +import type { Props } from "@theme/Admonition/Icon/Info"; + +export default function AdmonitionIconInfo(props: Props): ReactNode { + return ( + + + + ); +} diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/theme/Admonition/Icon/Note.tsx b/versioned_docs/version-0.12 (stable)/miden-vm/theme/Admonition/Icon/Note.tsx new file mode 100644 index 0000000..d7c524b --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-vm/theme/Admonition/Icon/Note.tsx @@ -0,0 +1,20 @@ +import React, { type ReactNode } from "react"; +import type { Props } from "@theme/Admonition/Icon/Note"; + +export default function AdmonitionIconNote(props: Props): ReactNode { + return ( + + + + ); +} diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/theme/Admonition/Icon/Tip.tsx b/versioned_docs/version-0.12 (stable)/miden-vm/theme/Admonition/Icon/Tip.tsx new file mode 100644 index 0000000..219bb8d --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-vm/theme/Admonition/Icon/Tip.tsx @@ -0,0 +1,20 @@ +import React, { type ReactNode } from "react"; +import type { Props } from "@theme/Admonition/Icon/Tip"; + +export default function AdmonitionIconTip(props: Props): ReactNode { + return ( + + + + ); +} diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/theme/Admonition/Icon/Warning.tsx b/versioned_docs/version-0.12 (stable)/miden-vm/theme/Admonition/Icon/Warning.tsx new file mode 100644 index 0000000..f96398d --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-vm/theme/Admonition/Icon/Warning.tsx @@ -0,0 +1,20 @@ +import React, { type ReactNode } from "react"; +import type { Props } from "@theme/Admonition/Icon/Warning"; + +export default function AdmonitionIconCaution(props: Props): ReactNode { + return ( + + + + ); +} diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/theme/Admonition/Layout/index.tsx b/versioned_docs/version-0.12 (stable)/miden-vm/theme/Admonition/Layout/index.tsx new file mode 100644 index 0000000..7b2c170 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-vm/theme/Admonition/Layout/index.tsx @@ -0,0 +1,51 @@ +import React, { type ReactNode } from "react"; +import clsx from "clsx"; +import { ThemeClassNames } from "@docusaurus/theme-common"; + +import type { Props } from "@theme/Admonition/Layout"; + +import styles from "./styles.module.css"; + +function AdmonitionContainer({ + type, + className, + children, +}: Pick & { children: ReactNode }) { + return ( +
+ {children} +
+ ); +} + +function AdmonitionHeading({ icon, title }: Pick) { + return ( +
+ {icon} + {/* {title} */} +
+ ); +} + +function AdmonitionContent({ children }: Pick) { + return children ? ( +
{children}
+ ) : null; +} + +export default function AdmonitionLayout(props: Props): ReactNode { + const { type, icon, title, children, className } = props; + return ( + + {title || icon ? : null} + {children} + + ); +} diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/theme/Admonition/Layout/styles.module.css b/versioned_docs/version-0.12 (stable)/miden-vm/theme/Admonition/Layout/styles.module.css new file mode 100644 index 0000000..88df7e6 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-vm/theme/Admonition/Layout/styles.module.css @@ -0,0 +1,35 @@ +.admonition { + margin-bottom: 1em; +} + +.admonitionHeading { + font: var(--ifm-heading-font-weight) var(--ifm-h5-font-size) / + var(--ifm-heading-line-height) var(--ifm-heading-font-family); + text-transform: uppercase; +} + +/* Heading alone without content (does not handle fragment content) */ +.admonitionHeading:not(:last-child) { + margin-bottom: 0.3rem; +} + +.admonitionHeading code { + text-transform: none; +} + +.admonitionIcon { + display: inline-block; + vertical-align: middle; + margin-right: 0.4em; +} + +.admonitionIcon svg { + display: inline-block; + height: 1.6em; + width: 1.6em; + fill: var(--ifm-alert-foreground-color); +} + +.admonitionContent > :last-child { + margin-bottom: 0; +} diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/theme/Admonition/Type/Caution.tsx b/versioned_docs/version-0.12 (stable)/miden-vm/theme/Admonition/Type/Caution.tsx new file mode 100644 index 0000000..b570a37 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-vm/theme/Admonition/Type/Caution.tsx @@ -0,0 +1,32 @@ +import React, {type ReactNode} from 'react'; +import clsx from 'clsx'; +import Translate from '@docusaurus/Translate'; +import type {Props} from '@theme/Admonition/Type/Caution'; +import AdmonitionLayout from '@theme/Admonition/Layout'; +import IconWarning from '@theme/Admonition/Icon/Warning'; + +const infimaClassName = 'alert alert--warning'; + +const defaultProps = { + icon: , + title: ( + + caution + + ), +}; + +// TODO remove before v4: Caution replaced by Warning +// see https://github.com/facebook/docusaurus/issues/7558 +export default function AdmonitionTypeCaution(props: Props): ReactNode { + return ( + + {props.children} + + ); +} diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/theme/Admonition/Type/Danger.tsx b/versioned_docs/version-0.12 (stable)/miden-vm/theme/Admonition/Type/Danger.tsx new file mode 100644 index 0000000..49901fa --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-vm/theme/Admonition/Type/Danger.tsx @@ -0,0 +1,30 @@ +import React, {type ReactNode} from 'react'; +import clsx from 'clsx'; +import Translate from '@docusaurus/Translate'; +import type {Props} from '@theme/Admonition/Type/Danger'; +import AdmonitionLayout from '@theme/Admonition/Layout'; +import IconDanger from '@theme/Admonition/Icon/Danger'; + +const infimaClassName = 'alert alert--danger'; + +const defaultProps = { + icon: , + title: ( + + danger + + ), +}; + +export default function AdmonitionTypeDanger(props: Props): ReactNode { + return ( + + {props.children} + + ); +} diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/theme/Admonition/Type/Info.tsx b/versioned_docs/version-0.12 (stable)/miden-vm/theme/Admonition/Type/Info.tsx new file mode 100644 index 0000000..018e0a1 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-vm/theme/Admonition/Type/Info.tsx @@ -0,0 +1,30 @@ +import React, {type ReactNode} from 'react'; +import clsx from 'clsx'; +import Translate from '@docusaurus/Translate'; +import type {Props} from '@theme/Admonition/Type/Info'; +import AdmonitionLayout from '@theme/Admonition/Layout'; +import IconInfo from '@theme/Admonition/Icon/Info'; + +const infimaClassName = 'alert alert--info'; + +const defaultProps = { + icon: , + title: ( + + info + + ), +}; + +export default function AdmonitionTypeInfo(props: Props): ReactNode { + return ( + + {props.children} + + ); +} diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/theme/Admonition/Type/Note.tsx b/versioned_docs/version-0.12 (stable)/miden-vm/theme/Admonition/Type/Note.tsx new file mode 100644 index 0000000..c99e038 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-vm/theme/Admonition/Type/Note.tsx @@ -0,0 +1,30 @@ +import React, {type ReactNode} from 'react'; +import clsx from 'clsx'; +import Translate from '@docusaurus/Translate'; +import type {Props} from '@theme/Admonition/Type/Note'; +import AdmonitionLayout from '@theme/Admonition/Layout'; +import IconNote from '@theme/Admonition/Icon/Note'; + +const infimaClassName = 'alert alert--secondary'; + +const defaultProps = { + icon: , + title: ( + + note + + ), +}; + +export default function AdmonitionTypeNote(props: Props): ReactNode { + return ( + + {props.children} + + ); +} diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/theme/Admonition/Type/Tip.tsx b/versioned_docs/version-0.12 (stable)/miden-vm/theme/Admonition/Type/Tip.tsx new file mode 100644 index 0000000..18604a5 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-vm/theme/Admonition/Type/Tip.tsx @@ -0,0 +1,30 @@ +import React, {type ReactNode} from 'react'; +import clsx from 'clsx'; +import Translate from '@docusaurus/Translate'; +import type {Props} from '@theme/Admonition/Type/Tip'; +import AdmonitionLayout from '@theme/Admonition/Layout'; +import IconTip from '@theme/Admonition/Icon/Tip'; + +const infimaClassName = 'alert alert--success'; + +const defaultProps = { + icon: , + title: ( + + tip + + ), +}; + +export default function AdmonitionTypeTip(props: Props): ReactNode { + return ( + + {props.children} + + ); +} diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/theme/Admonition/Type/Warning.tsx b/versioned_docs/version-0.12 (stable)/miden-vm/theme/Admonition/Type/Warning.tsx new file mode 100644 index 0000000..61d9597 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-vm/theme/Admonition/Type/Warning.tsx @@ -0,0 +1,30 @@ +import React, {type ReactNode} from 'react'; +import clsx from 'clsx'; +import Translate from '@docusaurus/Translate'; +import type {Props} from '@theme/Admonition/Type/Warning'; +import AdmonitionLayout from '@theme/Admonition/Layout'; +import IconWarning from '@theme/Admonition/Icon/Warning'; + +const infimaClassName = 'alert alert--warning'; + +const defaultProps = { + icon: , + title: ( + + warning + + ), +}; + +export default function AdmonitionTypeWarning(props: Props): ReactNode { + return ( + + {props.children} + + ); +} diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/theme/Admonition/Types.tsx b/versioned_docs/version-0.12 (stable)/miden-vm/theme/Admonition/Types.tsx new file mode 100644 index 0000000..2a10019 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-vm/theme/Admonition/Types.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import AdmonitionTypeNote from '@theme/Admonition/Type/Note'; +import AdmonitionTypeTip from '@theme/Admonition/Type/Tip'; +import AdmonitionTypeInfo from '@theme/Admonition/Type/Info'; +import AdmonitionTypeWarning from '@theme/Admonition/Type/Warning'; +import AdmonitionTypeDanger from '@theme/Admonition/Type/Danger'; +import AdmonitionTypeCaution from '@theme/Admonition/Type/Caution'; +import type AdmonitionTypes from '@theme/Admonition/Types'; + +const admonitionTypes: typeof AdmonitionTypes = { + note: AdmonitionTypeNote, + tip: AdmonitionTypeTip, + info: AdmonitionTypeInfo, + warning: AdmonitionTypeWarning, + danger: AdmonitionTypeDanger, +}; + +// Undocumented legacy admonition type aliases +// Provide hardcoded/untranslated retrocompatible label +// See also https://github.com/facebook/docusaurus/issues/7767 +const admonitionAliases: typeof AdmonitionTypes = { + secondary: (props) => , + important: (props) => , + success: (props) => , + caution: AdmonitionTypeCaution, +}; + +export default { + ...admonitionTypes, + ...admonitionAliases, +}; diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/theme/Admonition/index.tsx b/versioned_docs/version-0.12 (stable)/miden-vm/theme/Admonition/index.tsx new file mode 100644 index 0000000..8f4225d --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-vm/theme/Admonition/index.tsx @@ -0,0 +1,21 @@ +import React, {type ComponentType, type ReactNode} from 'react'; +import {processAdmonitionProps} from '@docusaurus/theme-common'; +import type {Props} from '@theme/Admonition'; +import AdmonitionTypes from '@theme/Admonition/Types'; + +function getAdmonitionTypeComponent(type: string): ComponentType { + const component = AdmonitionTypes[type]; + if (component) { + return component; + } + console.warn( + `No admonition component found for admonition type "${type}". Using Info as fallback.`, + ); + return AdmonitionTypes.info!; +} + +export default function Admonition(unprocessedProps: Props): ReactNode { + const props = processAdmonitionProps(unprocessedProps); + const AdmonitionTypeComponent = getAdmonitionTypeComponent(props.type); + return ; +} diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/tools/_category_.yml b/versioned_docs/version-0.12 (stable)/miden-vm/tools/_category_.yml new file mode 100644 index 0000000..af33c24 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-vm/tools/_category_.yml @@ -0,0 +1,4 @@ +label: "Development tooling" +# Determines where this documentation section appears relative to other sections in the parent folder +position: 5 +collapsed: true diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/tools/debugger.md b/versioned_docs/version-0.12 (stable)/miden-vm/tools/debugger.md new file mode 100644 index 0000000..e1b804c --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-vm/tools/debugger.md @@ -0,0 +1,62 @@ +--- +title: "Miden Debugger" +sidebar_position: 2 +--- + +# Miden Debugger + +The Miden debugger is a command-line interface (CLI) application, inspired by [GNU gdb](https://sourceware.org/gdb/), which allows debugging of Miden assembly (MASM) programs. The debugger allows the user to step through the execution of the program, both forward and backward, either per clock cycle tick, or via breakpoints. + +The Miden debugger supports the following commands: + +| Command | Shortcut | Arguments | Description | +| --- | --- | --- | --- | +| next | n | count? | Steps `count` clock cycles. Will step `1` cycle of `count` is omitted. | +| continue | c | - | Executes the program until completion, failure or a breakpoint. | +| back | b | count? | Backward step `count` clock cycles. Will back-step `1` cycle of `count` is omitted. | +| rewind | r | - | Executes the program backwards until the beginning, failure or a breakpoint. | +| print | p | - | Displays the complete state of the virtual machine. | +| print mem | p m | address? | Displays the memory value at `address`. If `address` is omitted, didisplays all the memory values. | +| print stack | p s | index? | Displays the stack value at `index`. If `index` is omitted, displays all the stack values. | +| clock | c | - | Displays the current clock cycle. | +| quit | q | - | Quits the debugger. | +| help | h | - | Displays the help message. | + +In order to start debugging, the user should provide a `MASM` program: + +```shell +cargo run --features executable -- debug --assembly miden-vm/masm-examples/nprime/nprime.masm +``` + +The expected output is: + +``` +============================================================ +Debug program +============================================================ +Reading program file `miden-vm/masm-examples/nprime/nprime.masm` +Compiling program... done (16 ms) +Debugging program with hash 11dbbddff27e26e48be3198133df8cbed6c5875d0fb +606c9f037c7893fde4118... +Reading input file `miden-vm/masm-examples/nprime/nprime.inputs` +Welcome! Enter `h` for help. +>> +``` + +In order to add a breakpoint, the user should insert a `breakpoint` instruction into the MASM file. This will generate a `Noop` operation that will be decorated with the debug break configuration. This is a provisory solution until the source mapping is implemented. + +The following example will halt on the third instruction of `foo`: + +``` +proc.foo + dup + dup.2 + breakpoint + swap + add.1 +end + +begin + exec.foo +end +``` diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/tools/index.md b/versioned_docs/version-0.12 (stable)/miden-vm/tools/index.md new file mode 100644 index 0000000..cc0e5f6 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-vm/tools/index.md @@ -0,0 +1,20 @@ +--- +title: "Development tooling" +sidebar_position: 1 +--- + +# Development Tools and Resources + +The following tools are available for interacting with Miden VM: + +- Via the [miden-vm](https://crates.io/crates/miden-vm) crate (or within the Miden VM repo): + - [CLI](../usage.md#cli-interface) + - [Debugger](./debugger.md) + - [REPL](./repl.md) +- Via your browser: + - The interactive [Miden VM Playground](https://0xMiden.github.io/examples/) for writing, executing, proving, and verifying programs from your browser. + +The following resources are available to help you get started programming with Miden VM more quickly: + +- The [Miden VM examples repo](https://github.com/0xMiden/examples) contains examples of programs written in Miden Assembly. +- The [Miden project template](https://github.com/0xMiden/project-template) provides a scaffold for starting new Miden projects in Rust. diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/tools/repl.md b/versioned_docs/version-0.12 (stable)/miden-vm/tools/repl.md new file mode 100644 index 0000000..f8ea3c8 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-vm/tools/repl.md @@ -0,0 +1,180 @@ +--- +title: "REPL" +sidebar_position: 3 +--- + +# Miden REPL + +The Miden Read–eval–print loop (REPL) is a Miden shell that allows for quick and easy debugging of Miden assembly. After the REPL gets initialized, you can execute any Miden instruction, undo executed instructions, check the state of the stack and memory at a given point, and do many other useful things! When the REPL is exited, a `history.txt` file is saved. One thing to note is that all the REPL native commands start with an `!` to differentiate them from regular assembly instructions. + +Miden REPL can be started via the CLI [repl](../usage.md#cli-interface) command like so: + +```Shell +./target/optimized/miden-vm repl +``` + +It is also possible to initialize REPL with libraries. To create it with Miden standard library you need to specify `-s` or `--stdlib` subcommand, it is also possible to add a third-party library by specifying `-l` or `--libraries` subcommand with paths to `.masl` library files. For example: + +```Shell +./target/optimized/miden-vm repl -s -l example/library.masl +``` + +### Miden assembly instruction + +All Miden instructions mentioned in the [Miden Assembly sections](../user_docs/assembly/index.md) are valid. One can either input instructions one by one or multiple instructions in one input. + +For example, the below two commands will result in the same output. + +``` +>> push.1 +>> push.2 +>> push.3 +``` + +``` +push.1 push.2 push.3 +``` + +To execute a control flow operation, one must write the entire statement in a single line with spaces between individual operations. + +``` +repeat.20 + pow2 +end +``` + +The above example should be written as follows in the REPL tool: + +``` +repeat.20 pow2 end +``` + +### !help + +The `!help` command prints out all the available commands in the REPL tool. + +### !program + +The `!program` command prints out the entire Miden program being executed. E.g., in the below scenario: + +``` +>> push.1.2.3.4 +>> repeat.16 pow2 end +>> u32wrapping_add + +>> !program +begin + push.1.2.3.4 + repeat.16 pow2 end + u32wrapping_add +end +``` + +### !stack + +The `!stack` command prints out the state of the stack at the last executed instruction. Since the stack always contains at least 16 elements, 16 or more elements will be printed out (even if all of them are zeros). + +``` +>> push.1 push.2 push.3 push.4 push.5 +>> exp +>> u32wrapping_mul +>> swap +>> eq.2 +>> assert +``` + +The `!stack` command will print out the following state of the stack: + +``` +>> !stack +3072 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +``` + +### !mem + +The `!mem` command prints out the contents of all initialized memory locations. For each such location, the address, along with its memory values, is printed. Recall that four elements are stored at each memory address. + +If the memory has at least one value that has been initialized: + +``` +>> !mem +7: [1, 2, 0, 3] +8: [5, 7, 3, 32] +9: [9, 10, 2, 0] +``` + +If the memory is not yet been initialized: + +``` +>> !mem +The memory has not been initialized yet +``` + +### !mem[addr] + +The `!mem[addr]` command prints out memory contents at the address specified by `addr`. + +If the `addr` has been initialized: + +``` +>> !mem[9] +9: [9, 10, 2, 0] +``` + +If the `addr` has not been initialized: + +``` +>> !mem[87] +Memory at address 87 is empty +``` + +### !use + +The `!use` command prints out the list of all modules available for import. + +If the stdlib was added to the available libraries list `!use` command will print all its modules: + +``` +>> !use +Modules available for importing: +std::collections::mmr +std::collections::smt +... +std::mem +std::sys +std::utils +``` + +Using the `!use` command with a module name will add the specified module to the program imports: + +``` +>> !use std::math::u64 + +>> !program +use.std::math::u64 + +begin + +end +``` + +### !undo + +The `!undo` command reverts to the previous state of the stack and memory by dropping off the last executed assembly instruction from the program. One could use `!undo` as often as they want to restore the state of a stack and memory $n$ instructions ago (provided there are $n$ instructions in the program). The `!undo` command will result in an error if no remaining instructions are left in the Miden program. + +``` +>> push.1 push.2 push.3 +>> push.4 +>> !stack +4 3 2 1 0 0 0 0 0 0 0 0 0 0 0 0 + +>> push.5 +>> !stack +5 4 3 2 1 0 0 0 0 0 0 0 0 0 0 0 + +>> !undo +4 3 2 1 0 0 0 0 0 0 0 0 0 0 0 0 + +>> !undo +3 2 1 0 0 0 0 0 0 0 0 0 0 0 0 0 +``` diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/usage.md b/versioned_docs/version-0.12 (stable)/miden-vm/usage.md new file mode 100644 index 0000000..41e34e9 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-vm/usage.md @@ -0,0 +1,173 @@ +--- +title: "Usage" +sidebar_position: 3 +--- + +# Usage + +Before you can use Miden VM, you'll need to make sure you have Rust [installed](https://www.rust-lang.org/tools/install). Miden VM v0.19 requires Rust version **1.90** or later. + +Miden VM consists of several crates, each of which exposes a small set of functionality. The most notable of these crates are: + +- [miden-processor](https://crates.io/crates/miden-processor), which can be used to execute Miden VM programs. +- [miden-prover](https://crates.io/crates/miden-prover), which can be used to execute Miden VM programs and generate proofs of their execution. +- [miden-verifier](https://crates.io/crates/miden-verifier), which can be used to verify proofs of program execution generated by Miden VM prover. + +The above functionality is also exposed via the single [miden-vm](https://crates.io/crates/miden-vm) crate, which also provides a CLI interface for interacting with Miden VM. + +## CLI interface + +### Compiling Miden VM + +To compile Miden VM into a binary, we have a [Makefile](https://www.gnu.org/software/make/manual/make.html) with the following tasks: + +```shell +make exec +``` + +This will place an optimized, multi-threaded `miden-vm` executable into the `./target/optimized` directory. It is equivalent to executing: + +```shell +cargo build --profile optimized --features concurrent,executable +``` + +If you would like to enable single-threaded mode, you can compile Miden VM using the following command: + +```shell +make exec-single +``` + +### Controlling parallelism + +Internally, Miden VM uses [rayon](https://github.com/rayon-rs/rayon) for parallel computations. To control the number of threads used to generate a STARK proof, you can use `RAYON_NUM_THREADS` environment variable. + +### GPU acceleration + +Miden VM proof generation can be accelerated via GPUs. Currently, GPU acceleration is enabled only on Apple Silicon hardware (via [Metal]()). To compile Miden VM with Metal acceleration enabled, you can run the following command: + +```shell +make exec-metal +``` + +Similar to `make exec` command, this will place the resulting `miden-vm` executable into the `./target/optimized` directory. + +Currently, GPU acceleration is applicable only to recursive proofs which can be generated using the `-r` flag. + +### SIMD acceleration + +Miden VM execution and proof generation can be accelerated via vectorized instructions. Currently, SIMD acceleration can be enabled on platforms supporting [SVE]() and [AVX2](https://en.wikipedia.org/wiki/Advanced_Vector_Extensions#Advanced_Vector_Extensions_2) instructions. + +To compile Miden VM with AVX2 acceleration enabled, you can run the following command: + +```shell +make exec-avx2 +``` + +To compile Miden VM with SVE acceleration enabled, you can run the following command: + +```shell +make exec-sve +``` + +This will place the resulting `miden-vm` executable into the `./target/optimized` directory. + +Similar to Metal acceleration, SVE/AVX2 acceleration is currently applicable only to recursive proofs which can be generated using the `-r` flag. + +### Running Miden VM + +Once the executable has been compiled, you can run Miden VM like so: + +```shell +./target/optimized/miden-vm [subcommand] [parameters] +``` + +Currently, Miden VM can be executed with the following subcommands: + +- `run` - this will execute a Miden assembly program and output the result, but will not generate a proof of execution. +- `prove` - this will execute a Miden assembly program, and will also generate a STARK proof of execution. +- `verify` - this will verify a previously generated proof of execution for a given program. +- `compile` - this will compile a Miden assembly program (i.e., build a program [MAST](./design/programs.md)) and outputs stats about the compilation process. +- `debug` - this will instantiate a [Miden debugger](./tools/debugger.md) against the specified Miden assembly program and inputs. +- `analyze` - this will run a Miden assembly program against specific inputs and will output stats about its execution. +- `repl` - this will initiate the [Miden REPL](./tools/repl.md) tool. +- `example` - this will execute a Miden assembly example program, generate a STARK proof of execution and verify it. Currently, it is possible to run `blake3` and `fibonacci` examples. + +All of the above subcommands require various parameters to be provided. To get more detailed help on what is needed for a given subcommand, you can run the following: + +```shell +./target/optimized/miden-vm [subcommand] --help +``` + +For example: + +```shell +./target/optimized/miden-vm prove --help +``` + +To execute a program using the Miden VM there needs to be a `.masm` file containing the Miden Assembly code and a `.inputs` file containing the inputs. + +#### Enabling logging + +You can use `MIDEN_LOG` environment variable to control how much logging output the VM produces. For example: + +```shell +MIDEN_LOG=trace ./target/optimized/miden-vm [subcommand] [parameters] +``` + +If the level is not specified, `warn` level is set as default. + +#### Enable Debugging features + +You can use the run command with `--debug` parameter to enable debugging with the [debug instruction](./user_docs/assembly/debugging.md) such as `debug.stack`: + +```shell +./target/optimized/miden-vm run [path_to.masm] --debug +``` + +### Inputs + +As described [here](https://0xMiden.github.io/miden-vm/intro/overview.html#inputs-and-outputs) the Miden VM can consume public and secret inputs. + +- Public inputs: + - `operand_stack` - can be supplied to the VM to initialize the stack with the desired values before a program starts executing. If the number of provided input values is less than 16, the input stack will be padded with zeros to the length of 16. The maximum number of the stack inputs is limited by 16 values, providing more than 16 values will cause an error. +- Secret (or nondeterministic) inputs: + - `advice_stack` - can be supplied to the VM. There is no limit on how much data the advice provider can hold. This is provided as a string array where each string entry represents a field element. + - `advice_map` - is supplied as a map of 64-character hex keys, each mapped to an array of numbers. The hex keys are interpreted as 4 field elements and the arrays of numbers are interpreted as arrays of field elements. + - `merkle_store` - the Merkle store is container that allows the user to define `merkle_tree`, `sparse_merkle_tree` and `partial_merkle_tree` data structures. + - `merkle_tree` - is supplied as an array of 64-character hex values where each value represents a leaf (4 elements) in the tree. + - `sparse_merkle_tree` - is supplied as an array of tuples of the form (number, 64-character hex string). The number represents the leaf index and the hex string represents the leaf value (4 elements). + - `partial_merkle_tree` - is supplied as an array of tuples of the form ((number, number), 64-character hex string). The internal tuple represents the leaf depth and index at this depth, and the hex string represents the leaf value (4 elements). + +_Check out the [comparison example](https://github.com/0xMiden/examples/blob/main/examples/comparison.masm) to see how secret inputs work._ + +After a program finishes executing, the elements that remain on the stack become the outputs of the program. Notice that the number of values on the operand stack at the end of the program execution can not be greater than 16, otherwise the program will return an error. The [`truncate_stack`](./user_docs/stdlib/sys.md) utility procedure from the standard library could be used to conveniently truncate the stack at the end of the program. + +## Fibonacci example + +In the `miden/masm-examples/fib` directory, we provide a very simple Fibonacci calculator example. This example computes the 1001st term of the Fibonacci sequence. You can execute this example on Miden VM like so: + +```shell +./target/optimized/miden-vm run miden-vm/masm-examples/fib/fib.masm +``` + +### Capturing Output + +This will run the example code to completion and will output the top element remaining on the stack. + +If you want the output of the program in a file, you can use the `--output` or `-o` flag and specify the path to the output file. For example: + +```shell +./target/optimized/miden-vm run miden-vm/masm-examples/fib/fib.masm -o fib.out +``` + +This will dump the output of the program into the `fib.out` file. The output file will contain the state of the stack at the end of the program execution. + +### Running with debug instruction enabled + +Inside `miden-vm/masm-examples/fib/fib.masm`, insert `debug.stack` instruction anywhere between `begin` and `end`. Then run: + +```shell +./target/optimized/miden-vm run miden-vm/masm-examples/fib/fib.masm -n 1 --debug +``` + +You should see output similar to "Stack state before step ..." diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/user_docs/_category_.yml b/versioned_docs/version-0.12 (stable)/miden-vm/user_docs/_category_.yml new file mode 100644 index 0000000..62ab4f4 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-vm/user_docs/_category_.yml @@ -0,0 +1,4 @@ +label: "User Documentation" +# Determines where this documentation section appears relative to other sections in the parent folder +position: 6 +collapsed: true diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/user_docs/assembly/_category_.yml b/versioned_docs/version-0.12 (stable)/miden-vm/user_docs/assembly/_category_.yml new file mode 100644 index 0000000..6b8f36c --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-vm/user_docs/assembly/_category_.yml @@ -0,0 +1,4 @@ +label: "Miden Assembly" +# Determines where this documentation section appears relative to other sections in the parent folder +position: 1 +collapsed: true diff --git a/versioned_docs/version-0.12 (stable)/miden-vm/user_docs/assembly/code_organization.md b/versioned_docs/version-0.12 (stable)/miden-vm/user_docs/assembly/code_organization.md new file mode 100644 index 0000000..a943ad7 --- /dev/null +++ b/versioned_docs/version-0.12 (stable)/miden-vm/user_docs/assembly/code_organization.md @@ -0,0 +1,258 @@ +--- +title: "Code Organization" +sidebar_position: 2 +--- + +## Code organization +A Miden assembly program is just a sequence of instructions each describing a specific directive or an operation. You can use any combination of whitespace characters to separate one instruction from another. + +In turn, Miden assembly instructions are just keywords which can be parameterized by zero or more parameters. The notation for specifying parameters is *keyword.param1.param2* - i.e., the parameters are separated by periods. For example, `push.123` instruction denotes a `push` operation which is parameterized by value `123`. + +Miden assembly programs are organized into procedures. Procedures, in turn, can be grouped into modules. + +### Procedures +A *procedure* can be used to encapsulate a frequently-used sequence of instructions which can later be invoked via a label. A procedure must start with a `proc.