<?xml version="1.0" encoding="utf-8" ?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:tt="http://teletype.in/" xmlns:opensearch="http://a9.com/-/spec/opensearch/1.1/"><title>Alexander B</title><subtitle>web3 dev / community first</subtitle><author><name>Alexander B</name></author><id>https://teletype.in/atom/alexanderblv</id><link rel="self" type="application/atom+xml" href="https://teletype.in/atom/alexanderblv?offset=0"></link><link rel="alternate" type="text/html" href="https://teletype.in/@alexanderblv?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=alexanderblv"></link><link rel="next" type="application/rss+xml" href="https://teletype.in/atom/alexanderblv?offset=10"></link><link rel="search" type="application/opensearchdescription+xml" title="Teletype" href="https://teletype.in/opensearch.xml"></link><updated>2026-04-09T10:28:19.757Z</updated><entry><id>alexanderblv:ZKSnarks101</id><link rel="alternate" type="text/html" href="https://teletype.in/@alexanderblv/ZKSnarks101?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=alexanderblv"></link><title>ZK-SNARKs 101. R1CS and Quadratic Programs</title><published>2025-10-31T12:45:50.781Z</published><updated>2025-10-31T12:45:50.781Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img2.teletype.in/files/d3/a0/d3a07971-51a3-4dd4-94aa-bee3fb3ae5d1.png"></media:thumbnail><summary type="html">Zero-knowledge SNARKs (zk-SNARKs)</summary><content type="html">
  &lt;p id=&quot;B8F2&quot;&gt;Zero-knowledge SNARKs (zk-SNARKs) are the cryptographic workhorse behind Aleo’s private smart contracts. They allow one to &lt;strong&gt;prove&lt;/strong&gt; in zero-knowledge that a computation was performed correctly (for example, satisfying some relations $F(x,w)=y$) without revealing any secret inputs $w$, yet with proofs that are &lt;strong&gt;succinct&lt;/strong&gt; (constant-sized) and fast to verify. Over the past decade the ecosystem of SNARK protocols has rapidly evolved. Early &lt;strong&gt;pairing-based&lt;/strong&gt; SNARKs like Groth16 required a unique &lt;em&gt;trusted setup&lt;/em&gt; for each circuit. Newer protocols (Sonic, PLONK, Marlin, etc.) shifted to &lt;em&gt;universal&lt;/em&gt; and &lt;em&gt;updatable&lt;/em&gt; setups at the cost of somewhat larger proofs or more prover work. Aleo’s SnarkVM (the private execution engine) and SnarkOS (the chain client) have similarly migrated from Groth16-like inner-SNARKs toward Marlin-based systems (specifically an Aleo-enhanced “Varuna” variant).&lt;/p&gt;
  &lt;figure id=&quot;Ku2z&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/fb/7a/fb7ae851-54f2-417f-89a2-5e722aa7dfd8.png&quot; width=&quot;1000&quot; /&gt;
    &lt;figcaption&gt;&lt;em&gt;Aleo’s hybrid architecture. On the client side&lt;/em&gt; (left) a user runs their Leo program in SnarkVM (possibly delegating proving to a third party) to generate a ZK-proof of the computation. The &lt;em&gt;network side&lt;/em&gt; (right) runs SnarkOS/AleoOS: validators include the proof (and public inputs/outputs) in a block and efficiently verify it, using AleoBFT consensus (green circles). This architecture decouples private, off-chain execution (proof generation) from on-chain verification.&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;7WwP&quot;&gt;&lt;/p&gt;
  &lt;h2 id=&quot;5YaH&quot;&gt;ZK-SNARKs 101. R1CS and Quadratic Programs&lt;/h2&gt;
  &lt;p id=&quot;dhc6&quot;&gt;At the core of most pairing-based SNARKs is the &lt;em&gt;Rank-1 Constraint System&lt;/em&gt; (R1CS) model. A computation (say a Leo smart contract function) is compiled into an arithmetic circuit over a finite field $\mathbb{F}$, with &lt;em&gt;wires&lt;/em&gt; and &lt;em&gt;gates&lt;/em&gt; (additions and multiplications). The R1CS view encodes this circuit by three sparse matrices $A,B,C$ and a vector of variables $z=(x,w)$ (public inputs $x$ and private witness $w$), enforcing the constraint&lt;/p&gt;
  &lt;figure id=&quot;356s&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/97/0d/970d6236-cc85-479f-89d3-fe9bd30f7864.png&quot; width=&quot;147&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;f3xC&quot;&gt;where “$\circ$” is entry-wise (Hadamard) product. Concretely, if $A_i$, $B_i$, $C_i$ are the $i$-th rows of $A,B,C$, this says for each gate $i$:&lt;/p&gt;
  &lt;figure id=&quot;mAXz&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/bd/6a/bd6aacec-ddd9-4357-b3e3-fc3bd623c113.png&quot; width=&quot;207&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;XK3s&quot;&gt;Groth16 (2016) was one of the first SNARKs to efficiently prove R1CS satisfiability. It does so by reducing the R1CS to a &lt;em&gt;Quadratic Arithmetic Program&lt;/em&gt; (QAP). The idea is elegant: treat each gate’s constraint as a polynomial equation. Define polynomials&lt;/p&gt;
  &lt;figure id=&quot;qrQx&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/31/c2/31c2f098-ecbe-4078-a7c6-d594d777598d.png&quot; width=&quot;162&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;rk9a&quot;&gt;that encode all the left-input, right-input, and output wiring of the circuit (via Lagrange interpolation over gate indices). Then one shows that for a correct witness $z$, the polynomial&lt;/p&gt;
  &lt;figure id=&quot;xvxx&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/3e/72/3e7237f4-8967-47d5-9fd9-19e193c003d6.png&quot; width=&quot;238&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;wpFX&quot;&gt;is divisible by the so-called &lt;em&gt;vanishing polynomial&lt;/em&gt;&lt;/p&gt;
  &lt;figure id=&quot;PCKO&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/81/c9/81c9619d-84c6-42eb-89f0-6b3110b373f7.png&quot; width=&quot;312&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;uJkF&quot;&gt;where $r_1,\dots,r_n$ are distinct points corresponding to each of the $n$ multiplication gates. In other words, $P(z)$ has &lt;em&gt;roots&lt;/em&gt; at every gate index if and only if each gate constraint holds. Formally there exists a quotient polynomial $H(z)$ such that&lt;/p&gt;
  &lt;figure id=&quot;qvWJ&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/5d/3d/5d3d41b3-2e9d-4961-a57d-71e9260179a2.png&quot; width=&quot;170&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;MMCn&quot;&gt;The SNARK prover’s task is to convince the verifier that &lt;em&gt;this polynomial divisibility holds&lt;/em&gt;, without revealing $H$ or the witness. Using a cryptographic &lt;strong&gt;trusted setup&lt;/strong&gt;, both parties sample a random secret field element $s$ (discarded afterward) and publish a &lt;em&gt;structured reference string&lt;/em&gt; containing commitments $g, g^s, g^{s^2},\dots,g^{s^d}$ in a pairing-friendly elliptic curve (degree $d$ covers circuit size). The prover then “evaluates” these polynomials at $z=s$ in the exponent: for instance, committing to the polynomial $P(z)$ by computing&lt;/p&gt;
  &lt;figure id=&quot;7tES&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/3a/b9/3ab91b81-68cc-486a-8c56-50f738ef1d42.png&quot; width=&quot;293&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;Zukc&quot;&gt;Similarly $T(s)$ and $H(s)$ are committed. Using the bilinear pairing $e(\cdot,\cdot)$, the verifier checks&lt;/p&gt;
  &lt;figure id=&quot;E50b&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/35/19/3519c946-450c-49d1-aa5e-83e84f1d7f20.png&quot; width=&quot;286&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;48jz&quot;&gt;where $g^T = g^T(s)$ encodes $T(s)$, ensuring $P(s) = H(s),T(s)$ in the exponent. By the Schwartz–Zippel lemma, a single random check like this suffices for high confidence in the polynomial identity. In practice, Groth16 does a clever three-term pairing check, but the core idea is this division check.&lt;/p&gt;
  &lt;p id=&quot;xqfb&quot;&gt;Importantly, Groth16’s proof consists of just &lt;strong&gt;three group elements&lt;/strong&gt; (on G1/G2), so verification takes only a couple of pairings — extremely fast and constant-time. However, the catch is the &lt;em&gt;trusted setup&lt;/em&gt;: each new circuit (i.e. new program or changed program logic) requires generating a fresh SRS [$g, g^s, g^{s^2},\dots$] and then securely destroying $s$. If $s$ is leaked or reused, &lt;em&gt;toxic waste&lt;/em&gt; could enable forging proofs. Thus, Groth16 offers minimal proof size and fast verification but at the cost of per-circuit ceremonies and a heavy initial overhead (a “one-time pad” for each circuit). In legacy Zcash-style UTXO systems this was acceptable (programs rarely change), but for a general smart contract platform it quickly becomes unmanageable.&lt;/p&gt;
  &lt;h2 id=&quot;iFm7&quot;&gt;Toward Universal SRS. Sonic and PLONK&lt;/h2&gt;
  &lt;p id=&quot;HcOj&quot;&gt;To avoid repeated trusted setups, the SNARK community invented &lt;em&gt;universal&lt;/em&gt; SRS schemes: generate one large common reference string that can handle many circuits (up to some size bound), and allow anyone to update it (adding new randomness) for extra security. Early such work includes &lt;strong&gt;Sonic&lt;/strong&gt; (2019) and &lt;strong&gt;PLONK&lt;/strong&gt; (2019-20), which build on Kate–Zaverucha–Goldberg (KZG) polynomial commitments. KZG commitments work as follows: pick a secret $\alpha$, publish the group powers $H = [g, \alpha g, \alpha^2 g,\dots,\alpha^d g]$ as the proving key. To commit to a polynomial $P(x)=\sum_{i=0}^d p_i x^i$, compute&lt;/p&gt;
  &lt;figure id=&quot;nJmt&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/bd/71/bd715ed3-05f9-4a1e-92bd-23c291e6a7c3.png&quot; width=&quot;374&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;4cZJ&quot;&gt;a single group element. This hides $P(x)$ since $\alpha$ is secret. Later one can prove an evaluation $P(t)=y$ by providing a short opening proof (just a few group elements) using pairings.&lt;/p&gt;
  &lt;p id=&quot;smAE&quot;&gt;&lt;strong&gt;PLONK&lt;/strong&gt; uses KZG commitments to encode all circuit polynomials at once (e.g. wires, gates, permutation polynomials) and then verifies via a multi-point opening argument. A key innovation is the &lt;em&gt;permutation (copy) argument&lt;/em&gt;: PLONK adds a polynomial that enforces that each wire’s value is consistently used across the circuit (so-called copy constraints). The result is a fully universal SRS: one KZG setup covers any circuit up to a size bound, and circuits are checked using only polynomial commitments and their openings. In practice, a PLONK proof is only moderately larger than Groth16’s (on the order of 3–5 group elements) and verification requires a handful of pairings. The prover is somewhat slower (since it opens and performs FFTs over the whole circuit), but no new setup is needed per circuit.&lt;/p&gt;
  &lt;p id=&quot;Huvi&quot;&gt;&lt;strong&gt;Sonic&lt;/strong&gt; was an intermediate step toward universal SNARKs. It also achieves a universal setup, but its protocol was more complex and had higher overhead. Sonic builds on the idea of polynomial commitment but must commit to multiple polynomials (including &lt;em&gt;helper&lt;/em&gt; polynomials) and does a linear-size verifiable computation. It was largely superseded by PLONK, which optimizes similar ideas. In essence, Sonic demonstrated that universal SNARKs were possible, but Marlin, PLONK, and others showed faster and smaller-proof constructions.&lt;/p&gt;
  &lt;h2 id=&quot;aH5V&quot;&gt;The Marlin Protocol. Algebraic Holographic Proofs&lt;/h2&gt;
  &lt;p id=&quot;x9JD&quot;&gt;Marlin (2020) combines the best of both worlds: like PLONK, it has a &lt;em&gt;universal and updatable SRS&lt;/em&gt;, but it further shrinks proof and verification cost via a new “holographic” PCP approach. Its core is an &lt;strong&gt;Algebraic Holographic Proof (AHP)&lt;/strong&gt; for R1CS, which essentially is an interactive PCP over low-degree polynomials. In high-level terms, Marlin works like this:&lt;/p&gt;
  &lt;ol id=&quot;HA3k&quot;&gt;
    &lt;li id=&quot;ExQy&quot;&gt;&lt;strong&gt;Universal Setup (SRS)&lt;br /&gt;&lt;/strong&gt;One sets a maximum circuit size $N$ and samples randomness $\alpha$ as above. Publish the full KZG parameters ${g,\alpha g,\alpha^2 g,\ldots,\alpha^N g}$ (and their pairing counterparts). This SRS is &lt;em&gt;updatable&lt;/em&gt; – anyone can multiply each element by a new secret and re-publish, without altering the prior ability to prove new circuits. The resulting SRS is universal for &lt;em&gt;all&lt;/em&gt; circuits up to size $N$.&lt;/li&gt;
    &lt;li id=&quot;9qXn&quot;&gt;&lt;strong&gt;Prover / Indexing&lt;br /&gt;&lt;/strong&gt;Given a particular R1CS instance (matrices $A,B,C$) and a witness $w$, the prover compiles these into certain low-degree polynomials (via the AHP indexer). Intuitively, think of encoding each constraint equation into polynomial form, similar to QAP. Marlin’s indexing produces polynomial vectors (labeled $a(X), b(X), c(X), \ldots$) whose evaluations on a domain ${1,\dots,n}$ correspond to $Az, Bz, Cz$.&lt;/li&gt;
    &lt;li id=&quot;jaGx&quot;&gt;&lt;strong&gt;Proof Generation (Prover)&lt;br /&gt;&lt;/strong&gt;The prover engages in a concise PCP: they commit (using KZG) to these indexed polynomials. Then via a Fiat-Shamir hash, the protocol randomly samples a field element $r$ and some challenge polynomials. The prover computes linear combinations and sum-checks of these polynomials &lt;em&gt;at the random point $r$&lt;/em&gt;, generating a short proof of correctness. Concretely, Marlin’s prover produces just a handful of group elements: essentially commitments to certain combined polynomials and their evaluation proofs. Thanks to an algebraic sum-check technique, the final proof size is &lt;strong&gt;constant&lt;/strong&gt; (a few G1 elements) independent of circuit size.&lt;/li&gt;
    &lt;li id=&quot;gJrD&quot;&gt;&lt;strong&gt;Verification&lt;br /&gt;&lt;/strong&gt;The verifier checks pairings on those few group elements. It verifies the KZG openings at the challenge point and the polynomial identity $P(r) = H(r),T(r)$, now done in a vectorized way thanks to the PCP collapse. Crucially, Marlin achieves &lt;strong&gt;constant-time verification&lt;/strong&gt;: one final check that runs in &lt;em&gt;$O(1)$ pairings&lt;/em&gt;. This means even massive circuits incur only a fixed, small verification cost.&lt;/li&gt;
  &lt;/ol&gt;
  &lt;p id=&quot;nZfh&quot;&gt;In other words, Marlin realized a &lt;em&gt;linear PCP&lt;/em&gt; over polynomials (holographic proof) that can be tied into KZG commitments. The fancy words aside, the practical upshot is impressive: &lt;strong&gt;Marlin proofs are a few hundred bytes&lt;/strong&gt;, verifiable in milliseconds, with no per-circuit trust ceremony beyond the one-time SRS generation.&lt;/p&gt;
  &lt;p id=&quot;rDRC&quot;&gt;Mathematically, a few highlights help intuition:&lt;/p&gt;
  &lt;ul id=&quot;dzKv&quot;&gt;
    &lt;li id=&quot;w11i&quot;&gt;&lt;strong&gt;Polynomial Commitment Base.&lt;/strong&gt; Like PLONK, Marlin uses KZG commitments. For a polynomial $Q(X)=\sum q_i X^i$, the commit is $g^{Q(\alpha)}$ (one group element). Opening $Q$ at a challenge $r$ yields a small proof of size $O(1)$.&lt;/li&gt;
    &lt;li id=&quot;c36f&quot;&gt;&lt;strong&gt;Combined Checks.&lt;/strong&gt; Marlin constructs polynomials for the three R1CS matrices $A,B,C$ (call their indexed versions $\tilde{A}(X),\tilde{B}(X),\tilde{C}(X)$ over the domain), along with auxiliary polynomials. The PCP step picks random challenges and checks an identity of the form A~(r) B~(r)−C~(r)=Q(r) T(r),\tilde{A}(r)\,\tilde{B}(r) - \tilde{C}(r) = Q(r)\,T(r), very much like the Groth16 idea but done in the exponent and collapsed via the random point $r$. (Here $T(r)$ is the vanishing polynomial of the domain.) The prover gives commitments to $\tilde{A},\tilde{B},\tilde{C},Q$ and proofs of their evaluations at $r$. The verifier uses pairings to check one final equation implying the constraint product holds.&lt;/li&gt;
    &lt;li id=&quot;t3BI&quot;&gt;&lt;strong&gt;Efficiency Gains.&lt;/strong&gt; Compared to Sonic, Marlin’s innovation was to reduce the number of such polynomial commitments and to optimize the linear checks. In practice, Marlin’s proof uses &lt;em&gt;fewer group elements&lt;/em&gt; than Sonic (by “several”, per implementations) and the verifier time is ~3× faster. Moreover, Marlin speeds up the prover dramatically (10× over Sonic) by clever arithmetic, making its prover time comparable to circuit-specific SNARKs.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;aorY&quot;&gt;To summarize, Marlin gives us the best of both worlds: &lt;strong&gt;short proofs + constant verification&lt;/strong&gt; (like Groth16) with &lt;strong&gt;only one universal setup&lt;/strong&gt; (like PLONK). In fact, Marlin’s designers reported a performance table (Figure 2.2 in their thesis) showing Marlin’s proof size is just a few group elements for both Groth16 and PLONK-sized circuits, and its proving and verification are very practical.&lt;/p&gt;
  &lt;h2 id=&quot;SuTL&quot;&gt;Trade-offs. Proof Size, Time, and Setup&lt;/h2&gt;
  &lt;p id=&quot;JFZS&quot;&gt;Every SNARK choice involves a trade-off. Here’s how Groth16, PLONK/Sonic, and Marlin compare in broad strokes:&lt;/p&gt;
  &lt;ul id=&quot;963l&quot;&gt;
    &lt;li id=&quot;iD4H&quot;&gt;&lt;strong&gt;Proof Size.&lt;/strong&gt; Groth16 wins with &lt;em&gt;tiny&lt;/em&gt; proofs (3 group elements ~ 95 bytes). PLONK/Marlin proofs are typically a bit larger (on the order of 3–6 group elements, say 150–300 bytes) because they include multiple commitments and openings. Still, hundreds of bytes is negligible for blockchain use. Sonic’s proofs are larger, which is one reason it fell out of favor.&lt;/li&gt;
    &lt;li id=&quot;po8r&quot;&gt;&lt;strong&gt;Verification Time.&lt;/strong&gt; All these pairing SNARKs have &lt;em&gt;O(1)&lt;/em&gt; verify time (constant number of pairings) once parameters are set, so verification is very fast. Groth16 requires 3 pairings, PLONK around 5, Marlin maybe 2–3 (Marlin’s clever design actually achieves fewer pairings than older systems). In practice these all verify in milliseconds for real circuits. STARKs or Bulletproofs, by contrast, have much larger proofs or verification cost.&lt;/li&gt;
    &lt;li id=&quot;spsl&quot;&gt;&lt;strong&gt;Proving Time.&lt;/strong&gt; Groth16 is relatively fast to prove (though large circuits still take time). PLONK and Marlin provers do extra work (FFT over the circuit, polynomial arithmetic). Sonic was notably slower. Marlin’s prover, however, was engineered for speed: it uses FFTs and linear algebra to get roughly Groth16-like speed (especially with good multi-threading). In short, Marlin’s prover is a few times slower than Groth16 in raw speed, but this is often acceptable for developers because it avoids repeated setups.&lt;/li&gt;
    &lt;li id=&quot;7AS2&quot;&gt;&lt;strong&gt;Trusted Setup.&lt;/strong&gt; This is often the deciding factor. Groth16’s &lt;em&gt;circuit-specific&lt;/em&gt; setup (one SRS per circuit) is a dealbreaker for dynamic smart contracts. Sonic/PLONK/Marlin have a &lt;em&gt;universal&lt;/em&gt; trusted setup: one ceremony generates a master SRS (plus updatability) covering all circuits up to a given size. After that, new programs require only a cheap public key derivation (no new randomness). The cost is that the universal SRS might be quite large (say gigabytes) if the max circuit size is huge. For Aleo, the team accepts that up-front: they ran an MPC to generate a massive SRS that covers the expected circuit sizes and allow updates.&lt;/li&gt;
    &lt;li id=&quot;HCqx&quot;&gt;&lt;strong&gt;Expressivity.&lt;/strong&gt; Some SNARKs support more than R1CS constraints. Plonky2 (from Polygon) and others allow custom gates or lookup arguments. Aleo’s current system, &lt;strong&gt;Varuna&lt;/strong&gt;, is an extension of Marlin that supports “generalized R1CS” with custom gates and lookup tables. This makes it more like PLONK in expressivity. Groth16 is locked into plain R1CS (addition and multiplication), whereas PLONK/Varuna can optimize with precomputed lookups (e.g. fast SHA or range proofs) within one argument.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;a31D&quot;&gt;A handy analogy: &lt;em&gt;think of each proof system as a way of giving away minimal hints about a secret computation.&lt;/em&gt; Groth16 is like giving a tiny fingerprint of the whole proof that only works for one precise program, whereas PLONK/Marlin is like carrying a “one-time master key” that can lock any door up to a certain size. Groth16’s key changes for each door (circuit), but PLONK/Marlin’s universal key fits many doors (circuits), as long as you don’t enlarge the door beyond the key’s design. The cost is that carrying the universal key bundle (SRS) is bulkier to begin with, but afterwards proving new doors is easy.&lt;/p&gt;
  &lt;h2 id=&quot;roVe&quot;&gt;Aleo’s Use of Marlin (Varuna) in SnarkVM&lt;/h2&gt;
  &lt;p id=&quot;KThf&quot;&gt;Aleo’s &lt;strong&gt;SnarkVM&lt;/strong&gt; synthesizer compiles Leo programs into R1CS and generates proofs. Early prototypes of Aleo did use Groth16 (for example, an “inner SNARK” model), but as Aleo matured the team settled on a Marlin-based system. In fact, Aleo’s code and documentation refer to &lt;strong&gt;Varuna&lt;/strong&gt;, a Marlin-derived proof system supporting custom gates. Varuna is essentially Marlin “+” extra flexibility: while Marlin as published only encodes R1CS (three matrices), Varuna supports additional arithmetic constraints (Plonkish lookup tables, Range gates, etc.) for better efficiency on typical operations (e.g. SHA/Keccak acceleration).&lt;/p&gt;
  &lt;p id=&quot;pV1p&quot;&gt;The Aleo Engine works roughly as follows: a user writes a Leo program and executes it locally in SnarkVM. This produces an R1CS that enforces the program logic and the current state transitions. The SnarkVM &lt;em&gt;prover&lt;/em&gt; (which can run on the user’s machine or a delegated server) then invokes Marlin/Varuna to produce a proof of correctness. That proof (along with the public inputs/outputs) is submitted as a transaction to SnarkOS. Validators quickly verify the proof using Marlin’s verification key (derived from Varuna’s universal setup).&lt;/p&gt;
  &lt;pre id=&quot;sp82&quot;&gt;// Pseudocode: Generating and verifying a Marlin proof in SnarkVM (Rust)
use snarkvm_marlin::{Marlin, SRS};
use snarkvm_r1cs::{Circuit, Prover, Verifier};

// 1. Load or generate universal SRS (trusted ceremony output) covering up to size N
let srs: SRS&amp;lt;bls12_377::Engine&amp;gt; = SRS::load_or_generate(&amp;quot;marlin_srs.params&amp;quot;, 2u64.pow(20))?;

// 2. Define the R1CS for our computation (e.g., a Leo function)
let circuit: Circuit&amp;lt;bls12_377::Fr&amp;gt; = MyLeoProgram::compile_to_r1cs()?;

// 3. Run setup (derives PK/VK from the universal SRS and this circuit)
let (pk, vk) = Marlin::setup(&amp;amp;srs, &amp;amp;circuit)?;

// 4. Prover creates a proof given a witness &amp;#x60;witness&amp;#x60;
let proof = Marlin::prove(&amp;amp;pk, &amp;amp;circuit, &amp;amp;witness)?;

// 5. Verifier checks the proof against the public inputs
let valid = Marlin::verify(&amp;amp;vk, &amp;amp;circuit.public_input(), &amp;amp;proof)?;
assert!(valid, &amp;quot;Proof failed verification&amp;quot;);
&lt;/pre&gt;
  &lt;p id=&quot;zsXr&quot;&gt;The above schematic code shows how one would use SnarkVM’s Marlin library to prove and verify an R1CS circuit. The real SnarkVM hides much of this complexity: it handles loading the SRS (downloaded once), synthesizing the circuit, and batching tasks. But under the hood it is exactly using Marlin (via an Arkworks-based implementation).&lt;/p&gt;
  &lt;p id=&quot;Yocc&quot;&gt;Because Marlin/Varuna is a preprocessing SNARK, Aleo must perform one big MPC ceremony to create the initial SRS for all its R1CS constraints. Aleo did this! The Aleo community ran a multi-party computation yielding universal parameters. After that, any change in an Aleo program (e.g. updating a smart contract) does &lt;em&gt;not&lt;/em&gt; require a new MPC—only re-compiling to R1CS and a quick local &lt;code&gt;setup&lt;/code&gt; step to derive the circuit-specific proving/verification keys from the universal SRS. This is a huge practical benefit for developers (no recurring ceremonies or trusted launch keys).&lt;/p&gt;
  &lt;p id=&quot;hYO1&quot;&gt;Finally, Aleo (SnarkOS) includes &lt;em&gt;proof-of-prover&lt;/em&gt; incentives (Aleo’s “puzzles”) which actually encourage people to build specialized proving hardware (even GPUs) to generate Marlin proofs faster. This means Aleo isn’t just using off-the-shelf SNARKs: it’s co-designing hardware and protocols. But regardless of hardware, the cryptographic core is this Marlin/Varuna framework.&lt;/p&gt;
  &lt;h2 id=&quot;C5w7&quot;&gt;Strengths, Limitations, and Aleo’s Edge&lt;/h2&gt;
  &lt;p id=&quot;Frpf&quot;&gt;&lt;strong&gt;Strengths.&lt;/strong&gt; Marlin/Varuna gives Aleo succinct proofs with &lt;em&gt;no per-circuit setup&lt;/em&gt;. Verification time is minimal (great for on-chain use), and proof sizes are small (keeps transaction bandwidth low). The SNARK supports general arithmetic circuits, so any Leo program can be proven. The algebraic design means proofs rely only on established assumptions (pairings) and enjoy full zero-knowledge. Aleo specifically leverages advanced features (custom gates, lookups) to make ZK-proving as efficient as possible for typical operations (hashes, etc.).&lt;/p&gt;
  &lt;p id=&quot;L1n5&quot;&gt;&lt;strong&gt;Limitations.&lt;/strong&gt; The initial trusted setup (SRS) is still a significant chore: you need a big ceremony and careful distribution. Also, polynomial-based SNARKs require a pairing-friendly curve (Aleo uses BLS12-377 by choice), which means they’re not post-quantum secure. The prover work, while optimized, is still substantial for very large circuits (hence Aleo’s interest in hardware acceleration). Compared to transparently setup STARKs, Marlin proofs are smaller but rely on trusted randomness and pairings. Lastly, if Aleo’s requirements outgrow its SRS bound, a new setup with a larger size would be needed (so Aleo must anticipate growth).&lt;/p&gt;
  &lt;p id=&quot;URQR&quot;&gt;&lt;strong&gt;What Makes Aleo Unique. &lt;/strong&gt;Aleo is one of the first platforms built from the ground up on zkSNARKs with general programmability. While many chains may use privacy SNARKs just for transactions, Aleo’s vision is &lt;em&gt;privacy-first programmability&lt;/em&gt;: write any logic in Leo and deploy it privately. The use of Marlin/Varuna is central to that: it provides a universal proving infrastructure. Aleo’s SnarkVM ties this into a full virtual machine. By open-sourcing SnarkVM and using the Marlin theory in code, Aleo lets developers experiment with ZK proofs as first-class artifacts. The &lt;em&gt;extensibility&lt;/em&gt; of this platform is key: developers could imagine adding new proof gadgets or recursive proofs down the line. In fact, Aleo’s roadmap discusses possible integration of “proof-carrying data” (Darlin, recursive composition) on top of Marlin.&lt;/p&gt;
  &lt;p id=&quot;PS7I&quot;&gt;The real practical upshot is: as an Aleo developer you get &lt;strong&gt;zero-knowledge by default&lt;/strong&gt;. Behind the scenes, SnarkVM uses these complex algebraic protocols to ensure your program’s execution is sound, without any extra effort on your part besides writing correct Leo code. The “magic” of SNARKs is hidden behind the familiar contract paradigm. But understanding Groth16→PLONK→Marlin illuminates how this magic works, and why Aleo chose Marlin/Varuna: it hits the sweet spot of efficiency and flexibility for a smart contract platform.&lt;/p&gt;

</content></entry><entry><id>alexanderblv:ProofEfficiencyinAleo</id><link rel="alternate" type="text/html" href="https://teletype.in/@alexanderblv/ProofEfficiencyinAleo?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=alexanderblv"></link><title>Proof Efficiency in Aleo. Optimizations for Finite Fields and Witness Construction</title><published>2025-10-31T12:43:36.829Z</published><updated>2025-10-31T12:43:36.829Z</updated><summary type="html">Aleo is a privacy-first blockchain that uses zk-SNARKs</summary><content type="html">
  &lt;p id=&quot;SWD5&quot;&gt;Aleo is a privacy-first blockchain that uses zk-SNARKs (via the &lt;strong&gt;Marlin&lt;/strong&gt; protocol) to prove general-purpose program execution. In Aleo’s architecture, user programs written in the high-level &lt;strong&gt;Leo&lt;/strong&gt; language are compiled by the SnarkVM synthesizer into R1CS circuits. The prover then computes a &lt;em&gt;witness&lt;/em&gt; (all intermediate values) and generates a succinct proof attesting to correct execution, all while keeping inputs private. In practice, proof generation is compute-intensive: Aleo’s documentation notes that “proof generation on Aleo is a compute-intensive process” that even client software often offloads to GPUs to speed things up. This is because the core proving algorithms involve massive finite-field arithmetic. Aleo’s provers predominantly perform two heavy tasks: &lt;strong&gt;Number-Theoretic Transforms (NTTs)&lt;/strong&gt; for fast polynomial arithmetic and &lt;strong&gt;Multi-Scalar Multiplications (MSMs)&lt;/strong&gt; in elliptic groups. In Aleo’s consensus (“Proving”) work, provers earn rewards by generating SNARK proofs that require many FFT/NTT operations and MSMs.&lt;/p&gt;
  &lt;h2 id=&quot;3Psf&quot;&gt;Fast Polynomial Arithmetic via NTT/FFT&lt;/h2&gt;
  &lt;p id=&quot;sbqQ&quot;&gt;At the heart of most modern SNARKs is a polynomial commitment scheme, which relies on fast polynomial multiplication and evaluation. Directly multiplying two degree-𝑛 polynomials takes $O(n^2)$ operations (naïve convolution), but by using a Fast Fourier Transform (FFT) over a finite field (an NTT), this drops to $O(n\log n)$. In Aleo’s field (the 377-bit base field of BLS12-377), one can choose a power-of-two domain size with a primitive root of unity and run a radix-2 FFT. Mathematically, an NTT evaluates a polynomial $f(x)=\sum_{i=0}^{n-1} a_i x^i$ at $n$ equally spaced &lt;em&gt;roots of unity&lt;/em&gt; in the field, transforming convolution into pointwise multiplication. For example:&lt;/p&gt;
  &lt;pre id=&quot;5sKH&quot;&gt;// Pseudocode (radix-2 decimation-in-time NTT)
function NTT(a[0..n-1], ω):
    if n == 1: return
    (even, odd) = split a into evens and odds
    NTT(even, ω^2) 
    NTT(odd,  ω^2) 
    x = 1
    for i in [0..n/2-1]:
        t = x * odd[i]
        a[i]        = even[i] + t
        a[i+n/2] = even[i] - t
        x = x * ω&lt;/pre&gt;
  &lt;p id=&quot;SB5Q&quot;&gt;Here $ω$ is an $n$-th root of unity in the field. The inverse NTT recovers the coefficients from the evaluations. In Aleo, these NTTs are used whenever polynomials are multiplied or when interpolating between coefficient and evaluation forms.&lt;/p&gt;
  &lt;p id=&quot;khCm&quot;&gt;The Marlin SNARK in Aleo performs dozens of such transforms. For instance, on Aleo’s Testnet2, each proof generation involved &lt;strong&gt;11 iterations&lt;/strong&gt; of FFT followed by MSM steps. (This aligns with the general observation that proof generation is dominated by FFT and MSM workloads.) Concretely, for a large circuit, each column of an AR1CS might require FFTs of length $L\approx32T$, costing roughly $2L\log L$ field multiplies per transform In that example, with $T=2^{20}$ steps, an FFT costs on the order of $1600\cdot T$ field operations. Thus even medium-sized proofs involve millions of multiplications.&lt;/p&gt;
  &lt;p id=&quot;h4LY&quot;&gt;Aleo’s implementation uses optimized NTT routines (e.g. in Arkworks or SnarkVM) to handle these efficiently. In practice, many NTT implementations break the transform into parallel “butterfly” operations, which can be multithreaded across CPU cores. The transform steps are fully data-parallel, so multicore CPUs or vectorized instructions can achieve significant speedups. Hardware acceleration is also possible: recent efforts like Tachyon (from the ZKAccelerate initiative) report &lt;strong&gt;~1.4× speedup&lt;/strong&gt; on NTT operations compared to software libraries.&lt;/p&gt;
  &lt;h2 id=&quot;QSvU&quot;&gt;Parallel Witness Construction and Proving&lt;/h2&gt;
  &lt;p id=&quot;20yt&quot;&gt;Beyond polynomial math, &lt;strong&gt;witness construction&lt;/strong&gt; (a.k.a. &lt;em&gt;synthesis&lt;/em&gt;) – executing the Leo program to compute all private- and public-input-dependent values – can also be parallelized to some extent. The SnarkVM synthesizer compiles control flow and arithmetic into a flat R1CS. Independent portions of a circuit can be evaluated in parallel; for example, if the program branches or processes arrays, each branch or array chunk’s witness values can be filled by separate threads. Additionally, low-level primitives (like range proofs, hash functions, etc.) often consist of many independent gate evaluations. In short, while some dependence remains (each operation may feed into the next), many constraint groups can be worked on concurrently.&lt;/p&gt;
  &lt;p id=&quot;Y8F9&quot;&gt;Once the witness is computed, the &lt;strong&gt;prover algorithm&lt;/strong&gt; follows. Modern SNARK backends like Marlin use highly parallel approaches. In particular, Multi-Scalar Multiplication (MSM) – the elliptic-curve exponentiation step in polynomial commitments (similar to KZG or inner-product arguments) – is embarrassingly parallel. MSM is often implemented with &lt;em&gt;Pippenger’s algorithm&lt;/em&gt;, which shards the scalar/vector multiplication across threads or GPU cores. Aleo’s own documentation notes that FFTs run on CPU while MSMs consume “CPU and GPU resources”, reflecting this division of labor. Indeed, GPU acceleration is well-suited to the large batch of elliptic multiplications in an MSM. A recent industry report observes that GPU hardware can &lt;em&gt;significantly&lt;/em&gt; speed up MSM: for example, Tachyon’s GPU-optimized MSM was reported as 1.8×–10× faster than standard libraries. The same report notes parallel speedups for FFT: Tachyon’s NTT was ~1.4× faster than Arkworks’ default FFT.&lt;/p&gt;
  &lt;p id=&quot;O12j&quot;&gt;Aleo is also developing &lt;strong&gt;batch-proving&lt;/strong&gt; optimizations. Marlin supports batching multiple proofs together so that common polynomial FFTs can be shared. The Aleo team plans to add “Marlin Batch Proving” to SnarkVM, which will amortize the cryptographic work across many proofs and greatly increase throughput for high transaction loads.&lt;/p&gt;
  &lt;h2 id=&quot;koYX&quot;&gt;Performance: Throughput and Benchmarks&lt;/h2&gt;
  &lt;p id=&quot;f2h6&quot;&gt;In aggregate, these optimizations pay off in dramatic performance gains. During its Testnet3 Phase II, Aleo reported a roughly &lt;strong&gt;37,000×&lt;/strong&gt; increase in proofs-per-second (PPS) over Testnet2. (Testnet2 averaged ~20,000 PPS network-wide; Testnet3 saw roughly three orders of magnitude more on similar hardware.) This was achieved by both hardware advances and algorithmic improvements. For example, Aleo adjusted its mining puzzle to focus on witness generation rather than final proof output, deliberately removing redundant FFT/MSM work– effectively shifting the “mining” workload to proof preparation.&lt;/p&gt;
  &lt;p id=&quot;74NG&quot;&gt;On the user side, proof times vary with circuit size. Simple operations (e.g. adding a few field elements) yield tiny circuits and proofs that can be generated in milliseconds on a modern CPU. Complex circuits (like big loops or cryptographic hashes) can have hundreds of thousands or millions of constraints and take seconds to minutes. For instance, before hardware acceleration, a large circuit took on the order of &lt;strong&gt;6 hours&lt;/strong&gt; to prove – but with ASIC/GPU acceleration this dropped to about &lt;strong&gt;10 minutes&lt;/strong&gt;. (This example comes from the “zkEmail” circuit at ZKAccelerate, but it illustrates the scale: a complex privacy app can be proven in under 10 minutes with optimized tooling.) Even without custom hardware, a heavily optimized CPU prover might generate a mid-sized proof in a matter of seconds to a minute.&lt;/p&gt;
  &lt;p id=&quot;vRjs&quot;&gt;Theory helps set expectations: if $n$ is the number of R1CS constraints, a Marlin proving run does roughly $O(n \log n)$ field operations for the FFTs plus $O(n)$ EC operations for MSM. Thus doubling constraints typically more than doubles time. In practice, the constant factors are large: one estimate suggests an FFT on ~$10^6$ points requires on the order of $10^9$ field multiplications. However, modern implementations exploit every parallel resource. Aleo users often run SnarkVM with multi-threading, and projects like LeoWallet even experiment with &lt;em&gt;browser-based GPU acceleration&lt;/em&gt; to further cut times.&lt;/p&gt;
  &lt;h2 id=&quot;PnLM&quot;&gt;Comparing ZK Systems: Cost, Usability, Scalability&lt;/h2&gt;
  &lt;p id=&quot;Co74&quot;&gt;Aleo’s approach has trade-offs relative to other ZK platforms. Like many SNARK systems (Groth16, Plonk, etc.), Aleo’s proofs are &lt;strong&gt;small&lt;/strong&gt; (on the order of 1–2 kilobytes) and &lt;strong&gt;fast to verify&lt;/strong&gt;, thanks to elliptic-curve cryptography. By contrast, STARK-based systems have much larger proofs (tens of kilobytes) but require no trusted setup. In Aleo, the use of Marlin means a &lt;strong&gt;universal SRS&lt;/strong&gt; is needed (one ceremony covers all programs). This avoids per-program setup, but requires an initial generation of parameters (Aleo’s universal setup took ~36 hours of computation). In return, Aleo gets quick proof times and the ability to prove arbitrary circuits without new ceremonies.&lt;/p&gt;
  &lt;p id=&quot;6bN7&quot;&gt;On the usability front, Aleo offers a high-level language (Leo) and tooling designed for developers. Leo is a familiar imperative/functional language, as opposed to lower-level DSLs like Circom or Cairo. Aleo’s documentation emphasizes that developers “can build any kind of application on Aleo… irrespective of how much computing power it needs,” combining privacy and expressiveness. The SnarkVM stack hides much of the cryptographic complexity, automatically optimizing and parallelizing the heavy math under the hood. In contrast, some ZK environments (e.g. Circom or Noir) require more manual optimization or management of constraints.&lt;/p&gt;
  &lt;p id=&quot;thYM&quot;&gt;Scalability is another dimension. Aleo decouples proof generation from consensus: users submit proofs as transactions, and a distributed network of &lt;strong&gt;provers&lt;/strong&gt; (incentivized hardware rigs) race to compute them. This “proof-of-succinct-work” model means Aleo can scale ZK computation by adding more provers (GPUs, ASICs, etc.), much like adding miners in a blockchain. By contrast, systems like zk-rollups batch proofs on a single L1, or StarkNet miners compete on STARK tasks. Aleo’s specialized prover network and upcoming batch-proofing feature should further boost throughput.&lt;/p&gt;
  &lt;p id=&quot;NRgo&quot;&gt;Finally, it’s worth noting that Aleo’s cryptographic costs align with industry norms: as a universal-SNARK, it still “spends” most time in FFTs and MSMs just like Plonk or Marlin. Hardware acceleration is being pursued across the board. Aleo’s elegance lies in integrating these known optimizations into a full-stack privacy platform. The SnarkVM compiler applies loop unrolling, constraint folding, and other circuit optimizations on Leo code, ensuring minimal R1CS size. Its record-based state model naturally limits the number of updated constraints per transaction. Combined with Marlin’s fast prover and small proofs, this makes Aleo competitive: developers get privacy “for free” at verification time, paying mostly in prover compute.&lt;/p&gt;

</content></entry><entry><id>alexanderblv:EncryptionMechanismsinAleo</id><link rel="alternate" type="text/html" href="https://teletype.in/@alexanderblv/EncryptionMechanismsinAleo?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=alexanderblv"></link><title>Encryption Mechanisms in Aleo. From ElGamal to Viewing Keys.</title><published>2025-10-31T12:36:49.324Z</published><updated>2025-10-31T12:37:02.658Z</updated><summary type="html">Encryption Mechanisms in Aleo</summary><content type="html">
  &lt;p id=&quot;0TFj&quot;&gt;Aleo’s privacy model encrypts all &lt;strong&gt;record&lt;/strong&gt; data by default, using an Elliptic-curve ElGamal–style scheme built for zero-knowledge proofs. In Aleo, each &lt;em&gt;record&lt;/em&gt; (the analog of a UTXO) includes an owner’s address, a data payload, and a unique nonce. The record’s commitment is computed as a SNARK-friendly Pedersen/PRF commitment over these fields. For example, a record commitment &lt;code&gt;cm&lt;/code&gt; is formed as &lt;code&gt;cm = CM.Commit(pp, v‖apk‖d‖ρ; r)&lt;/code&gt;, where &lt;code&gt;apk&lt;/code&gt; is the owner’s public address key, &lt;code&gt;d&lt;/code&gt; is the payload, &lt;code&gt;ρ&lt;/code&gt; is the one-time &lt;strong&gt;nonce&lt;/strong&gt;, and &lt;code&gt;r&lt;/code&gt; is hiding randomness. This commitment binds the owner and data while hiding contents on-chain. (The diagram below illustrates how the address, payload and nonce feed into the record commitment.)&lt;/p&gt;
  &lt;figure id=&quot;4KEX&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/7a/94/7a94415f-ad1c-4f79-b15a-a1be43f7f2a5.png&quot; width=&quot;1000&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;VRUt&quot;&gt;&lt;em&gt;Figure: Aleo record commitment (simplified). Each record &lt;code&gt;r&lt;/code&gt; produces a commitment &lt;code&gt;cm = CM.Commit(pp, visibility||owner_address||data||nonce; randomness)&lt;/code&gt;, binding the owner’s public key (&lt;code&gt;apk&lt;/code&gt;), payload, and unique nonce &lt;code&gt;ρ&lt;/code&gt; into the committed record【73†】.&lt;/em&gt;&lt;/p&gt;
  &lt;p id=&quot;RpYd&quot;&gt;With the record structure committed on-chain, Aleo encrypts the actual payload using an ECIES-like scheme. In practice, Aleo’s &lt;em&gt;record encryption&lt;/em&gt; works like an elliptic-curve ElGamal: the sender derives a shared secret with the receiver’s public address and then uses it to mask the record data. Concretely, the sender chooses a fresh random &lt;strong&gt;nonce&lt;/strong&gt; (scalar) and publishes the corresponding public “nonce point” in the record. This plays the role of the ephemeral ElGamal public key. Both sender and receiver compute a shared secret &lt;code&gt;S = owner_address * nonce&lt;/code&gt;; taking its x-coordinate gives a &lt;em&gt;symmetric record view key&lt;/em&gt;. This shared secret is fed into a Poseidon-based PRF to generate one-time masking values that encrypt each private field of the record. The use of Poseidon (a SNARK-friendly hash) and elliptic-curve Diffie-Hellman (ECDH) ensures the scheme is compatible with ZK circuits. In code (SnarkVM/Rust), one finds:&lt;/p&gt;
  &lt;pre id=&quot;JJuB&quot;&gt;rustКопировать код// Pseudocode from SnarkVM:
// Ensure &amp;#x60;nonce_point = G * randomizer&amp;#x60; matches the record’s nonce
assert(nonce == G * randomizer);
// Derive shared secret (x-coordinate of EC Diffie-Hellman)
let record_view_key = (owner_public_key * randomizer).to_x_coordinate();
// Encrypt data with derived key via Poseidon-based symmetric cipher
self.encrypt_symmetric(record_view_key);
&lt;/pre&gt;
  &lt;p id=&quot;Esoq&quot;&gt;This matches the Aleo implementation: the &lt;code&gt;encrypt&lt;/code&gt; method computes &lt;code&gt;record_view_key = (*self.owner * randomizer).to_x_coordinate()&lt;/code&gt; and then calls a Poseidon-based &lt;code&gt;encrypt_symmetric&lt;/code&gt; routine. In other words, Aleo’s &lt;strong&gt;symmetric record key&lt;/strong&gt; = ECDH(owner_pub, nonce), and encryption = Poseidon-PRF masking (rather than e.g. AES) for SNARK efficiency.&lt;/p&gt;
  &lt;h2 id=&quot;wNLM&quot;&gt;Ephemeral Key Generation and Record Encryption&lt;/h2&gt;
  &lt;p id=&quot;iUkm&quot;&gt;Each Aleo transaction (called a &lt;em&gt;transition&lt;/em&gt;) generates fresh ephemeral keys for encrypting its output records. The sender first derives a &lt;strong&gt;transition view key&lt;/strong&gt; by hashing their own account key with randomness. From this seed the sender obtains a one-time EC keypair: a &lt;em&gt;transition secret (view) key&lt;/em&gt; and its public key. This “transition public key” is published with the transaction so that owners of the outputs can decrypt. Then for each output record, the sender chooses a random scalar “randomizer” = nonce and computes &lt;code&gt;nonce_point = G * randomizer&lt;/code&gt;, embedding it in the record. The shared secret is &lt;code&gt;S = receiver_address * randomizer&lt;/code&gt;, whose x-coordinate (a field element) is the &lt;strong&gt;record view key&lt;/strong&gt;. This key is used to mask the record’s private fields via Poseidon.&lt;/p&gt;
  &lt;p id=&quot;nUSY&quot;&gt;In summary, the record encryption flow is:&lt;/p&gt;
  &lt;ol id=&quot;0UV5&quot;&gt;
    &lt;li id=&quot;dm4T&quot;&gt;&lt;strong&gt;Key Derivation:&lt;/strong&gt; Compute &lt;code&gt;S = P_recipient * r&lt;/code&gt;, where &lt;code&gt;r&lt;/code&gt; is the randomizer (nonce) and &lt;code&gt;P_recipient&lt;/code&gt; is the owner’s address point.&lt;/li&gt;
    &lt;li id=&quot;W5dI&quot;&gt;&lt;strong&gt;Symmetric Key:&lt;/strong&gt; Let &lt;code&gt;K = x(S)&lt;/code&gt; be the x-coordinate of &lt;code&gt;S&lt;/code&gt;. This is the shared secret (record view key) for this record.&lt;/li&gt;
    &lt;li id=&quot;yatO&quot;&gt;&lt;strong&gt;Encryption:&lt;/strong&gt; Expand &lt;code&gt;K&lt;/code&gt; via Poseidon PRF to produce keystream blocks, XOR-ing/masking each private field of the record.&lt;/li&gt;
  &lt;/ol&gt;
  &lt;p id=&quot;oIzL&quot;&gt;The Lambdaclass implementation succinctly shows this: it asserts &lt;code&gt;nonce == G * randomizer&lt;/code&gt;, then computes &lt;code&gt;record_view_key = (owner.to_group() * randomizer).to_x_coordinate()&lt;/code&gt;, and finally encrypts with &lt;code&gt;encrypt_symmetric(record_view_key)&lt;/code&gt;. Because &lt;code&gt;randomizer&lt;/code&gt; (the nonce) is fresh per record, each encryption is one-time padded by a unique ECDH key.&lt;/p&gt;
  &lt;p id=&quot;pnd0&quot;&gt;&lt;strong&gt;Security note:&lt;/strong&gt; Although new ECDH secrets are used per record, Aleo’s scheme is known to be &lt;em&gt;non-committing&lt;/em&gt;. In other words, the ciphertext alone does not bind to a specific plaintext without the view key. A recent audit flags “Non-Committing Encryption” in Aleo’s input/output encryption as a high-risk issue. In practice this means one should rely on Aleo’s built-in proof system (and cautious contract patterns) to prevent malleability attacks, since an encrypted record could in principle be tampered with and re-encrypted without detection. (This subtlety is common in Turing-complete ZKP systems.)&lt;/p&gt;
  &lt;h2 id=&quot;NsDP&quot;&gt;Viewing Keys and Selective Decryption&lt;/h2&gt;
  &lt;p id=&quot;CO77&quot;&gt;Aleo accounts have a private &lt;strong&gt;view key&lt;/strong&gt; and a corresponding public address key. The address &lt;code&gt;apk&lt;/code&gt; is essentially an elliptic-curve public key (a combination of the user’s signing and PRF public keys). The view key &lt;code&gt;avk&lt;/code&gt; is a private scalar derived from the account’s secret values. Cryptographically, the view key is the secret corresponding to the address: one can think of &lt;code&gt;apk = avk * G&lt;/code&gt; in the underlying group.&lt;/p&gt;
  &lt;p id=&quot;RC3k&quot;&gt;Only someone with the correct view key can derive the shared secret to decrypt a record. When a user scans the blockchain, they see each record’s ciphertext and nonce. They compute &lt;code&gt;K = avk * nonce_point&lt;/code&gt;. If their &lt;code&gt;avk&lt;/code&gt; matches the intended owner (so that &lt;code&gt;avk * nonce_point = S&lt;/code&gt;), this recovers the same shared secret used by the sender. Feeding &lt;code&gt;K&lt;/code&gt; into the Poseidon PRF yields the masking stream, allowing recovery of the plaintext values. If the wrong key is used, decryption fails.&lt;/p&gt;
  &lt;p id=&quot;TIZc&quot;&gt;Importantly, Aleo’s design allows &lt;strong&gt;selective disclosure&lt;/strong&gt;: an account holder may share their view key with auditors or services. Anyone holding the private view key &lt;code&gt;avk&lt;/code&gt; can decrypt all records owned by that account (and only those records). This enables transparent auditing of encrypted history. But without the view key, encrypted record data remains opaque.&lt;/p&gt;
  &lt;p id=&quot;IiIl&quot;&gt;In practice, Aleo provides a CLI for these operations. For example, one may run:&lt;/p&gt;
  &lt;pre id=&quot;BeBj&quot;&gt;php-templateКопировать кодsnarkos developer decrypt -v &amp;lt;VIEW_KEY&amp;gt; -c &amp;lt;RECORD_CIPHERTEXT&amp;gt;
&lt;/pre&gt;
  &lt;p id=&quot;grK3&quot;&gt;to decode a ciphertext with a given view key. The &lt;code&gt;Decrypt&lt;/code&gt; command outputs the original record fields if the key matches. Conversely, record creation/encryption is handled automatically by Aleo’s SDK or snarkOS when sending a transaction. (The SDK takes care of generating nonces and computing the &lt;code&gt;encrypt_symmetric&lt;/code&gt; step internally.)&lt;/p&gt;
  &lt;h2 id=&quot;oBK9&quot;&gt;Security Analysis&lt;/h2&gt;
  &lt;p id=&quot;m6FX&quot;&gt;The security of Aleo’s record encryption rests on standard ECC assumptions. Assuming the hardness of the elliptic-curve Diffie–Hellman problem, an adversary cannot recover the shared secret from the public address and nonce alone. The Poseidon PRF is cryptographically secure under standard hashing assumptions in the SNARK field. Thus Aleo’s encryption is &lt;em&gt;indistinguishable under chosen-plaintext attack&lt;/em&gt; (IND-CPA) for record payloads.&lt;/p&gt;
  &lt;p id=&quot;Hl5i&quot;&gt;A unique nonce per record provides &lt;em&gt;anti-replay&lt;/em&gt;: any attempt to reuse a record ciphertext or its nonce will produce a duplicate serial number (nullifier), which the ledger rejects as a double-spend The nonces ensure each record has a unique serial (&lt;code&gt;sn = PRF(skPRF, ρ)&lt;/code&gt;) so that replaying an old transaction is infeasible.&lt;/p&gt;
  &lt;p id=&quot;Nnw5&quot;&gt;However, Aleo’s encryption does &lt;strong&gt;not&lt;/strong&gt; provide forward secrecy against key compromise: if an account’s view key &lt;code&gt;avk&lt;/code&gt; is later leaked, an attacker can decrypt all past records for that account, since all ephemeral nonces are published on-chain. Aleo assumes long-term secrecy of the view key for confidentiality. On the other hand, because new Ephemeral &lt;em&gt;transition&lt;/em&gt; keys are used per transaction, compromise of one transition key does not break others.&lt;/p&gt;
  &lt;p id=&quot;JsYi&quot;&gt;The correctness of encryption/decryption is integrated into the ZK proofs. In each transition, the prover must demonstrate consistency of encryptions and decryptions within the circuit, ensuring that the committed and revealed values match. This proves both &lt;strong&gt;correctness&lt;/strong&gt; and prevents arbitrary tampering (beyond the non-committing issue noted above) under the Aleo proof system.&lt;/p&gt;
  &lt;h2 id=&quot;Az4F&quot;&gt;Code Examples&lt;/h2&gt;
  &lt;p id=&quot;FK4k&quot;&gt;Developers can interact with Aleo encryption via the &lt;code&gt;snarkos&lt;/code&gt; CLI and SDK. For instance, to decrypt a record’s ciphertext with a view key, one uses:&lt;/p&gt;
  &lt;pre id=&quot;gVpc&quot;&gt;snarkos developer decrypt -v AViewKey1nKB4qr... -c eyJhbGciOi...&amp;lt;ciphertext&amp;gt;...&lt;/pre&gt;
  &lt;p id=&quot;LJDB&quot;&gt;This command takes a Base58‐encoded view key and a record ciphertext string, and returns the plaintext contents of the record if the key is correct. (The view key format and address prefixes are defined in the account key docs.)&lt;/p&gt;
  &lt;p id=&quot;Idu8&quot;&gt;Records themselves are created within Aleo programs by emitting new record values (e.g. with &lt;code&gt;output r as RecordType.private&lt;/code&gt;). The Aleo VM automatically handles encryption when building the transaction. In Rust (SnarkVM), encryption looks like this snippet (from the Arkworks implementation):&lt;/p&gt;
  &lt;pre id=&quot;oEDN&quot;&gt;// Example from lambdaclass/aleo_lambda_vm
// Given owner (account address) and randomizer scalar:
let record_view_key = (owner.to_group() * randomizer).to_x_coordinate();
// This value encrypts the record symmetrically:
let ciphertext = record.encrypt_symmetric(record_view_key);&lt;/pre&gt;
  &lt;p id=&quot;REO1&quot;&gt;This matches how Aleo’s backend enforces &lt;code&gt;nonce == G * randomizer&lt;/code&gt; and computes &lt;code&gt;owner_pub * randomizer&lt;/code&gt; as the shared key. After this, all private fields of the record are masked by the Poseidon-PRF stream derived from &lt;code&gt;record_view_key&lt;/code&gt;.&lt;/p&gt;
  &lt;h2 id=&quot;Xr7b&quot;&gt;Comparison with Zcash and Ethereum Stealth Approaches&lt;/h2&gt;
  &lt;p id=&quot;vOQv&quot;&gt;Aleo’s scheme is conceptually similar to Zcash’s note encryption but differs in implementation and scope. In Zcash (Sapling/Orchard), each note is also encrypted to a recipient using an ephemeral Diffie–Hellman key: the sender computes a shared secret with the recipient’s public spend/view keys, then uses KDF and symmetric ciphers (AES/SHA3) to encrypt the note plaintext. Aleo likewise uses ECDH to derive a shared key and a symmetric cipher (Poseidon instead of AES) to mask data. Both use &lt;em&gt;view keys&lt;/em&gt;: Zcash has an “incoming viewing key” (IVK) that allows a recipient to decrypt notes sent to them. Aleo’s view key plays the same role for record data. (Zcash even defines an &lt;em&gt;outgoing viewing key&lt;/em&gt; so senders can later recover their own sent notes; Aleo’s model is simpler, treating the sender as always knowing the plaintext they create.) The Zcash Rust library calls this the “in-band secret distribution” scheme.&lt;/p&gt;
  &lt;p id=&quot;iVbY&quot;&gt;Ethereum’s &lt;strong&gt;stealth addresses&lt;/strong&gt; are also based on similar ECDH ideas, but with important differences. In a stealth scheme (as proposed in EIP-5564), Alice picks a random &lt;code&gt;r&lt;/code&gt;, computes &lt;code&gt;R = G*r&lt;/code&gt; and publishes &lt;code&gt;R&lt;/code&gt;. She shares funds to Bob by creating an address &lt;code&gt;P = Bob_pub + G*H(S)&lt;/code&gt; where &lt;code&gt;S = Bob_priv * R&lt;/code&gt;. Bob can compute the corresponding private key &lt;code&gt;b = Bob_priv + H(S)&lt;/code&gt; and spend from &lt;code&gt;P&lt;/code&gt;. Vitalik describes this process: Alice computes &lt;code&gt;S = M * r&lt;/code&gt; and the stealth public key &lt;code&gt;P = M + G*hash(S)&lt;/code&gt;, while Bob computes the private key &lt;code&gt;m + hash(S)&lt;/code&gt;. Stealth addresses hide the link between sender and receiver on-chain, but they &lt;em&gt;do not encrypt transaction values&lt;/em&gt; (only the destination address is hidden). In contrast, Aleo encrypts the &lt;em&gt;contents&lt;/em&gt; of a record (amounts or state) on-chain, not just the address. Ethereum’s approach also typically requires off-chain scanning of transaction nonces (ephemeral public keys) to find funds. Aleo always reveals the encrypted record on-chain, but only holders of the view key can recover it.&lt;/p&gt;
  &lt;p id=&quot;Y0E9&quot;&gt;In summary, all three use elliptic-curve Diffie–Hellman: Aleo and Zcash use it for full data encryption of private fields, while Ethereum stealth uses it for address generation. Aleo’s innovation is integrating this into a general-purpose ZK VM with SNARK-friendly hashes, whereas Zcash built it into a UTXO-style shielded protocol and Ethereum stealth is an ad-hoc privacy layer atop accounts.&lt;/p&gt;
  &lt;h2 id=&quot;UVFo&quot;&gt;Privacy, Auditability, and Performance Trade-offs&lt;/h2&gt;
  &lt;p id=&quot;5pSb&quot;&gt;Aleo maximizes &lt;strong&gt;privacy by default&lt;/strong&gt;: all record fields flagged private are encrypted and hidden from peers. This provides strong confidentiality, but comes at computational cost. Every private transaction requires elliptic-curve operations and hash-based symmetric encryption inside the proof. Poseidon is efficient in SNARKs, but still heavier than cleartext operations. Moreover, large payloads (complex state) mean more encrypted data and PRF blocks to compute. Thus privacy comes with throughput and latency trade-offs compared to a transparent chain.&lt;/p&gt;
  &lt;p id=&quot;u2eF&quot;&gt;Auditability is enabled by view keys. A user can choose to share their view key with regulators, auditors or dApps, allowing them to see the encrypted record values while keeping on-chain privacy. This granular control is more flexible than Ethereum’s default transparency or Zcash’s model (where a single “full viewing key” reveals all notes). In Aleo, one could even derive a read-only &lt;em&gt;compute key&lt;/em&gt; from the private key, granting limited access without exposing signing power.&lt;/p&gt;
  &lt;p id=&quot;QaWi&quot;&gt;Compared to alternatives, Aleo’s scheme is relatively lightweight: it encrypts only record payloads, not the entire state or transaction. Zcash’s note encryption also handles large notes (value + memo), but uses AES/SHA which are fast but not SNARK-friendly. Aleo’s use of Poseidon optimizes prover time. Ethereum stealth addresses add privacy for addresses, but to hide values one needs separate tools (e.g. zk rollups or mixers). Each choice has its trade-offs: Aleo favors on-chain data confidentiality at the expense of proof complexity, Zcash balances QAP-friendly ciphers with specialized note structures, and Ethereum stealth addresses focus only on unlinkability of accounts.&lt;/p&gt;
  &lt;p id=&quot;PaNZ&quot;&gt;In all cases, achieving private transfers means sacrificing some transparency and adding cryptography. Aleo’s design carefully balances these concerns: it delivers full-value encryption and auditing support, while using ZK-friendly primitives (Poseidon, EC groups) to keep proving efficient. The result is a system where professional developers and cryptographers can reason about security (under standard ECC assumptions) yet still enjoy programmable privacy in practice.&lt;/p&gt;

</content></entry><entry><id>alexanderblv:LeoTypes</id><link rel="alternate" type="text/html" href="https://teletype.in/@alexanderblv/LeoTypes?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=alexanderblv"></link><title>Type System in Leo. Enforcing Privacy Through Language-Level Guarantees</title><published>2025-09-30T12:11:07.976Z</published><updated>2025-09-30T12:11:07.976Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img2.teletype.in/files/dc/14/dc14de1e-dd07-4397-a96b-6b6df98b4980.png"></media:thumbnail><summary type="html">&lt;img src=&quot;https://img4.teletype.in/files/7d/82/7d828518-c60e-40d5-b5fd-87f69e1eca02.png&quot;&gt;Leo makes privacy a type: values are public/private (private by default), the compiler blocks leaks and enforces non-interference, and visibility propagates across rich types and UTXO records—privacy-by-construction, stronger than ZoKrates/Circom and similar to Noir.</summary><content type="html">
  &lt;p id=&quot;5Uxy&quot;&gt;Leo is a &lt;strong&gt;statically typed&lt;/strong&gt; programming language designed for writing privacy-preserving smart contracts on the Aleo. Its type system is not an afterthought – it is the very &lt;em&gt;tool&lt;/em&gt; by which privacy is enforced. In Leo, &lt;strong&gt;privacy is a compile-time property&lt;/strong&gt;, not just a runtime choice. By default, any value in a Leo program is &lt;strong&gt;private&lt;/strong&gt;, and only when a programmer explicitly marks it as &lt;code&gt;public&lt;/code&gt; is it treated as visible outside the circuit. This “private-by-default” model means that Leo’s type checker and compiler guarantee at &lt;strong&gt;compile time&lt;/strong&gt; that private data never leaks into public outputs unless the developer explicitly allows it.&lt;/p&gt;
  &lt;p id=&quot;IF9e&quot;&gt;Why is this important? In a traditional language you might mark some variables as secret, but nothing prevents you from accidentally using them in a print statement or sending them in a network message. Leo’s type system is more like a strict &lt;strong&gt;data flow police&lt;/strong&gt;: it tags every value as either &lt;code&gt;public&lt;/code&gt; or &lt;code&gt;private&lt;/code&gt;, and then stops the program from compiling if any illegal information flow is detected. This is crucial in zero-knowledge programming, where accidentally revealing a secret value can break the privacy guarantees of the entire protocol.&lt;/p&gt;
  &lt;p id=&quot;GxBG&quot;&gt;In the following sections, we dissect Leo’s type system and illustrate how it elegantly ensures privacy. We will cover:&lt;/p&gt;
  &lt;ul id=&quot;nbc9&quot;&gt;
    &lt;li id=&quot;B4xa&quot;&gt;&lt;strong&gt;Privacy qualifiers -&lt;/strong&gt; how &lt;code&gt;public&lt;/code&gt; vs. &lt;code&gt;private&lt;/code&gt; types work (Section 2).&lt;/li&gt;
    &lt;li id=&quot;BOPz&quot;&gt;&lt;strong&gt;Enforcement at compile time -&lt;/strong&gt; why the compiler &lt;em&gt;knows&lt;/em&gt; a private value wasn’t leaked.&lt;/li&gt;
    &lt;li id=&quot;I2Ve&quot;&gt;&lt;strong&gt;Data structures and ADTs -&lt;/strong&gt; &lt;code&gt;struct&lt;/code&gt;, &lt;code&gt;record&lt;/code&gt;, &lt;code&gt;enum&lt;/code&gt;, and how default visibility works (Section 3).&lt;/li&gt;
    &lt;li id=&quot;kaJD&quot;&gt;&lt;strong&gt;Polymorphism and generics -&lt;/strong&gt; traits, generic types, and inference (Section 4).&lt;/li&gt;
    &lt;li id=&quot;yfnX&quot;&gt;&lt;strong&gt;External interactions -&lt;/strong&gt; how types behave with mappings, records, and cross-contract calls (Section 5).&lt;/li&gt;
    &lt;li id=&quot;Lv7H&quot;&gt;&lt;strong&gt;Comparisons -&lt;/strong&gt; contrasts with other ZK languages (ZoKrates, Circom, Noir) where type systems are weaker (Section 6).&lt;/li&gt;
    &lt;li id=&quot;UFWM&quot;&gt;&lt;strong&gt;Real-world implications -&lt;/strong&gt; what problems Leo’s type system solves (Section 7).&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;WN0g&quot;&gt;Throughout, we use code snippets, informal analogies, and occasional mathematical intuition. For instance, one can think of Leo’s type system as a security guard: if a &lt;code&gt;private&lt;/code&gt; value tries to sneak into a &lt;code&gt;public&lt;/code&gt; slot, the guard stops the program at compile time (no runtime glitches, no “oops I leaked the secret!” runtime bugs). We aim to leave the reader with both conceptual clarity and technical depth.&lt;/p&gt;
  &lt;h2 id=&quot;Wd9Z&quot;&gt;2. Privacy Qualifiers. Public vs. Private Types&lt;/h2&gt;
  &lt;p id=&quot;oeVc&quot;&gt;A cornerstone of Leo’s type system is the &lt;strong&gt;visibility qualifier&lt;/strong&gt; on variables and fields. Each value in Leo is either &lt;strong&gt;public&lt;/strong&gt; (visible to all) or &lt;strong&gt;private&lt;/strong&gt; (kept secret in the SNARK witness). By default, everything is private. Only when the programmer writes the keyword &lt;code&gt;public&lt;/code&gt; before a declaration does Leo treat that data as public. Formally, Leo distinguishes:&lt;/p&gt;
  &lt;ul id=&quot;gr9W&quot;&gt;
    &lt;li id=&quot;XUFr&quot;&gt;&lt;strong&gt;Private types -&lt;/strong&gt; values known only to the prover, encrypted in the proof witness.&lt;/li&gt;
    &lt;li id=&quot;CRGj&quot;&gt;&lt;strong&gt;Public types - &lt;/strong&gt;values revealed on-chain and visible to any verifier or observer.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;wSzU&quot;&gt;Consider a simple transaction function:&lt;/p&gt;
  &lt;pre id=&quot;DR8t&quot;&gt;transition add_private_number(public a: u32, private b: u32) -&amp;gt; u32 {
    let c: u32 = a + b;
    return c;
}&lt;/pre&gt;
  &lt;p id=&quot;pic2&quot;&gt;Here &lt;code&gt;a&lt;/code&gt; is marked &lt;code&gt;public&lt;/code&gt;, while &lt;code&gt;b&lt;/code&gt; is &lt;code&gt;private&lt;/code&gt; (we could have omitted &lt;code&gt;private&lt;/code&gt;, since that’s the default). The transition adds them and returns &lt;code&gt;c&lt;/code&gt;. Because we &lt;em&gt;did not&lt;/em&gt; mark the return type as public, &lt;code&gt;c&lt;/code&gt; is treated as &lt;strong&gt;private&lt;/strong&gt; by default. In English: only the prover knows &lt;code&gt;b&lt;/code&gt; and &lt;code&gt;c&lt;/code&gt;; &lt;code&gt;a&lt;/code&gt; is public. The network can verify that &lt;code&gt;c = a + b&lt;/code&gt; without learning &lt;code&gt;b&lt;/code&gt; or &lt;code&gt;c&lt;/code&gt; itself. As the &lt;strong&gt;Provable Security blog&lt;/strong&gt; explains: “As &lt;code&gt;b&lt;/code&gt; and &lt;code&gt;c&lt;/code&gt; are private, the values in the transaction are encrypted and not revealed... The network ensures &lt;code&gt;c&lt;/code&gt; is the sum of &lt;code&gt;a&lt;/code&gt; and &lt;code&gt;b&lt;/code&gt; without knowing the value of &lt;code&gt;b&lt;/code&gt; and &lt;code&gt;c&lt;/code&gt;. In short, Leo’s type system has enforced &lt;em&gt;privacy by default&lt;/em&gt;.&lt;/p&gt;
  &lt;p id=&quot;xre1&quot;&gt;This strict tagging makes Leo a &lt;em&gt;flow-sensitive&lt;/em&gt; type system: it knows &lt;em&gt;where&lt;/em&gt; each piece of data came from. In particular:&lt;/p&gt;
  &lt;ul id=&quot;bmH1&quot;&gt;
    &lt;li id=&quot;Tb2Y&quot;&gt;&lt;strong&gt;Function parameters and variables.&lt;/strong&gt; You can declare a function or transition parameter as &lt;code&gt;public&lt;/code&gt; or &lt;code&gt;private&lt;/code&gt;. If omitted, it’s private. (Example: &lt;code&gt;fn example(x: u64, public y: u64) -&amp;gt; u64&lt;/code&gt; means &lt;code&gt;x&lt;/code&gt; is private, &lt;code&gt;y&lt;/code&gt; is public.)&lt;/li&gt;
    &lt;li id=&quot;RUEu&quot;&gt;&lt;strong&gt;Constant and owner.&lt;/strong&gt; The special keyword &lt;code&gt;constant&lt;/code&gt; marks compile-time constants. (E.g. &lt;code&gt;const MAX: u32 = 100u32&lt;/code&gt;.) Such constants are neither public nor private in the execution sense.&lt;/li&gt;
    &lt;li id=&quot;vUxq&quot;&gt;&lt;strong&gt;Return values. &lt;/strong&gt;Similarly, return types in signatures can be given a visibility. For example, &lt;code&gt;-&amp;gt; public u64&lt;/code&gt; would indicate a public output. If not given, outputs default to private as well.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;GbKi&quot;&gt;The Leo &lt;strong&gt;docs on program structure&lt;/strong&gt; make this clear: “A visibility can be either &lt;code&gt;constant&lt;/code&gt;, &lt;code&gt;public&lt;/code&gt;, or &lt;code&gt;private&lt;/code&gt;. Users may also omit the visibility, in which case, Leo will default to &lt;code&gt;private&lt;/code&gt;. This applies to record fields, function parameters, etc. A practical upshot: &lt;em&gt;if you don’t explicitly make something public, it stays secret&lt;/em&gt;. Thus the programmer must consciously decide what data to reveal.&lt;/p&gt;
  &lt;p id=&quot;ZE4I&quot;&gt;Why is this powerful? Because the compiler uses these qualifiers to &lt;em&gt;prevent mistakes&lt;/em&gt;. Imagine you try to compile a Leo program that returns a private value from a transaction (which is off-chain) as if it were public. The compiler will either assume that output is private (if unspecified) or flag an error if there is a mismatch. Likewise, if you accidentally write &lt;code&gt;return secret_value;&lt;/code&gt; from a function declared &lt;code&gt;-&amp;gt; public field&lt;/code&gt;, Leo will notice that &lt;code&gt;secret_value&lt;/code&gt; has private type and complain. In effect, the type checker enforces an information-flow rule: &lt;strong&gt;no private value can flow into a public channel&lt;/strong&gt; without an explicit conversion. (There is no implicit conversion; the only way to reveal a secret is to explicitly mark it &lt;code&gt;public&lt;/code&gt; in the code.)&lt;/p&gt;
  &lt;p id=&quot;s0TC&quot;&gt;Technically, one can think of this as a simple form of &lt;strong&gt;non-interference&lt;/strong&gt;: at compile time, the type checker knows that two executions with different private inputs must produce the same public outputs for the program to be safe. If a public output depends on a private input, Leo flags it. In practice, Leo accomplishes this by making you annotate public channels and then checking consistency. A helpful analogy: imagine every &lt;code&gt;private&lt;/code&gt; value carries a red tag, and every &lt;code&gt;public&lt;/code&gt; slot has a green tag. The compiler refuses to let a red-tagged box sit on a green shelf. Only if you change the tag to green (by writing &lt;code&gt;public&lt;/code&gt;) can it go on the green shelf.&lt;/p&gt;
  &lt;p id=&quot;yhGC&quot;&gt;Birgitta Arnet’s developer blog highlights this idea simply: in a transition signature like &lt;code&gt;(public a: field, b: field)&lt;/code&gt;, parameter &lt;code&gt;a&lt;/code&gt; is public but &lt;code&gt;b&lt;/code&gt; (no qualifier) is private. The write-up emphasizes: “The use of &lt;code&gt;public&lt;/code&gt; and &lt;code&gt;private&lt;/code&gt; modifiers in function parameters is essential for controlling the visibility of data... Public inputs are visible to all network participants, while private inputs remain confidential”. In other words, Leo’s type system is literally enforcing visibility of each input.&lt;/p&gt;
  &lt;p id=&quot;GBQD&quot;&gt;&lt;strong&gt;Key takeaway -&lt;/strong&gt; &lt;em&gt;In Leo’s type system, every value is tagged public or private. By default everything is private. The compiler uses these tags to ensure that secret data never slips into public outputs.&lt;/em&gt; This guarantee is baked into the language syntax and type checking, rather than being an afterthought.&lt;/p&gt;
  &lt;h2 id=&quot;fsWL&quot;&gt;3. Types and Data Structures: ADTs and Defaults&lt;/h2&gt;
  &lt;p id=&quot;3HQq&quot;&gt;Leo offers a rich set of data types and structures, all within this privacy framework. Some highlights:&lt;/p&gt;
  &lt;ul id=&quot;EAZ4&quot;&gt;
    &lt;li id=&quot;iqB7&quot;&gt;&lt;strong&gt;Scalar types.&lt;/strong&gt; integers (&lt;code&gt;u32&lt;/code&gt;, &lt;code&gt;i64&lt;/code&gt;, etc.), fields (&lt;code&gt;field&lt;/code&gt; for SNARK fields), booleans, addresses, etc. These are annotated just like in Rust (&lt;code&gt;42u32&lt;/code&gt; for a &lt;code&gt;u32&lt;/code&gt;, or &lt;code&gt;true&lt;/code&gt; for a &lt;code&gt;bool&lt;/code&gt;). Casting is possible (e.g. &lt;code&gt;let x: u16 = (y as u16);&lt;/code&gt;), and arithmetic is checked for overflow/underflow by the type checker/runtime.&lt;/li&gt;
    &lt;li id=&quot;mL3d&quot;&gt;&lt;strong&gt;Tuples and arrays.&lt;/strong&gt; Fixed-length tuples &lt;code&gt;(u8, bool, field)&lt;/code&gt; and arrays &lt;code&gt;[u32]&lt;/code&gt; are supported. Their element types also carry privacy qualifiers implicitly from their context (the whole tuple would be private if any element is private).&lt;/li&gt;
    &lt;li id=&quot;fX57&quot;&gt;&lt;strong&gt;Records (Ledger UTXOs). &lt;/strong&gt;Perhaps the most important ADT is the &lt;strong&gt;record type&lt;/strong&gt;, which models on-chain state (like UTXOs). A record is declared as follows: &lt;code&gt;record Token { owner: address, amount: u64, } &lt;/code&gt; According to the &lt;strong&gt;Leo docs&lt;/strong&gt;, each field in a record must have a visibility tag (public/private) or omit it. In the snippet above, we omitted them, so &lt;strong&gt;both &lt;code&gt;owner&lt;/code&gt; and &lt;code&gt;amount&lt;/code&gt; are private by default&lt;/strong&gt;. The documentation explicitly notes: &lt;em&gt;“All the fields in the &lt;code&gt;Token&lt;/code&gt; record are private by default”&lt;/em&gt;. At compile time, Leo will treat the entire record’s contents as encrypted witness data. Only the record &lt;em&gt;name&lt;/em&gt; (the fact a &lt;code&gt;Token&lt;/code&gt; exists) and any public fields are visible on-chain; private fields are cryptographically protected. There is also always an implicit &lt;code&gt;owner: address&lt;/code&gt; field (as shown) and a hidden &lt;code&gt;_nonce: group&lt;/code&gt; component for anti-replay. The key point: &lt;strong&gt;record fields default to private&lt;/strong&gt;. The only way to make a field public (and thus readable on-chain) is to write &lt;code&gt;public field_name: Type&lt;/code&gt;. This is rarely done for secret data (it defeats privacy) but can be used for things like &lt;code&gt;expire_at&lt;/code&gt; timestamps if needed.&lt;/li&gt;
    &lt;li id=&quot;pv2o&quot;&gt;&lt;strong&gt;Structs (Plain ADTs).&lt;/strong&gt; Leo also supports &lt;code&gt;struct&lt;/code&gt; types, which are just memory data bundles (not ledger entries). A struct looks the same as a record but is meant for in-program use. For example: &lt;code&gt;struct Message { sender: address, content: u32, } let msg = Message { sender: caller.address, content: 42u32 }; &lt;/code&gt; Here, like record fields, you could tag fields public or private. However, structs are not stored on-chain in the UTXO set by themselves; they can be passed around between functions. (In practice, Leo rarely distinguishes in syntax between struct and record fields – except that records have the special owner semantics.)&lt;/li&gt;
    &lt;li id=&quot;f6g8&quot;&gt;&lt;strong&gt;Enums (Sum Types).&lt;/strong&gt; Leo supports algebraic &lt;code&gt;enum&lt;/code&gt; types, similar to Rust or Swift. For example, one can define: &lt;code&gt;enum Fruit { Apple, Banana, Orange, } let favorite: Fruit = Fruit::Apple; &lt;/code&gt; This is confirmed by examples in Leo tutorials. Enums allow a variable to take one of several named variants. Each variant may optionally hold data (e.g. &lt;code&gt;Some(5)&lt;/code&gt;). Internally, enums are typed, and Leo’s type checker knows which variant is in use.&lt;/li&gt;
    &lt;li id=&quot;BBZ2&quot;&gt;&lt;strong&gt;Option and Result (Maybe types).&lt;/strong&gt; Leo provides &lt;code&gt;Option&amp;lt;T&amp;gt;&lt;/code&gt; and &lt;code&gt;Result&amp;lt;T,E&amp;gt;&lt;/code&gt; for optional values and error handling (as shown in tutorials). These are generic types. For example, a division function can return &lt;code&gt;Result&amp;lt;u64, str&amp;gt;&lt;/code&gt;: &lt;code&gt;fn divide(a: u64, b: u64) -&amp;gt; Result&amp;lt;u64, str&amp;gt; { if b == 0u64 { return Err(&amp;quot;Division by zero&amp;quot;); } return Ok(a / b); } &lt;/code&gt; This &lt;code&gt;Result&amp;lt;u64, str&amp;gt;&lt;/code&gt; means the function either returns &lt;code&gt;Ok(value)&lt;/code&gt; of type &lt;code&gt;u64&lt;/code&gt; or an &lt;code&gt;Err(error_message)&lt;/code&gt; string. Leo’s compiler and pattern matching can elegantly handle this as a sum type.&lt;/li&gt;
    &lt;li id=&quot;ZtmZ&quot;&gt;&lt;strong&gt;Polymorphism &amp;amp; Traits.&lt;/strong&gt; Leo allows &lt;strong&gt;generic (parametric) polymorphism&lt;/strong&gt;. Functions and structs can be written with type parameters &lt;code&gt;T&lt;/code&gt;. For instance, an identity function can be generic: &lt;code&gt;fn id&amp;lt;T&amp;gt;(x: T) -&amp;gt; T { return x; } &lt;/code&gt; The Leo docs and examples mention generics as a feature. Additionally, Leo has &lt;strong&gt;traits&lt;/strong&gt; (interfaces). One can define a trait with methods and then implement it for different types. For example &lt;code&gt;trait Shape { fn area(self) -&amp;gt; f64; } struct Circle { radius: f64, } impl Shape for Circle { fn area(self) -&amp;gt; f64 { 3.14159 * self.radius * self.radius } } &lt;/code&gt; Now any &lt;code&gt;Circle&lt;/code&gt; has an &lt;code&gt;area()&lt;/code&gt; method by the &lt;code&gt;Shape&lt;/code&gt; trait. Leo uses a Rust-like syntax (&lt;code&gt;impl Shape for Type&lt;/code&gt;). The compiler ensures at compile time that the &lt;code&gt;area&lt;/code&gt; function type-checks and that you can only call &lt;code&gt;area()&lt;/code&gt; on types that implement &lt;code&gt;Shape&lt;/code&gt;.&lt;/li&gt;
    &lt;li id=&quot;wIfY&quot;&gt;&lt;strong&gt;Type Defaults and Inference.&lt;/strong&gt; Leo requires type annotations in some places (e.g. numeric literals need a suffix like &lt;code&gt;u32&lt;/code&gt;) but also does &lt;strong&gt;type inference&lt;/strong&gt; in many contexts. The compiler will infer variable types from context when obvious (as long as it’s unambiguous). For example: &lt;code&gt;let x = 5u8; // annotation on literal let y = x + 2u8; // infers y: u8 let z: field = x * y; // now z is field type as annotated &lt;/code&gt; The formal Leo paper describes the inference engine: it will solve for omitted types uniquely or else raise errors. In effect, Leo’s type checker performs Hindley-Milner style inference on local variables and function return types, as long as annotations are provided on exposed APIs. This makes the syntax cleaner.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;0rmL&quot;&gt;In summary, Leo provides &lt;strong&gt;rich ADTs&lt;/strong&gt; (structs, records, enums, traits, generics) like modern typed languages. Crucially, &lt;strong&gt;every type carries visibility&lt;/strong&gt; (public vs private). By default, &lt;strong&gt;record and struct fields are private&lt;/strong&gt;. This means that if you simply declare a field without &lt;code&gt;public&lt;/code&gt;, Leo treats its content as secret. The on-chain &lt;em&gt;record&lt;/em&gt; (UTXO) will appear encrypted. Only the non-secret parts (like a public field or the mere existence of the record) are revealed.&lt;/p&gt;
  &lt;p id=&quot;KgV1&quot;&gt;An important nuance - even though a field is private, you &lt;em&gt;can&lt;/em&gt; move its value by logic, but doing so is monitored. For instance, if a transition consumes a record with a private field and then in the transition body you assign that field to some variable, you’re still in the safe off-chain zone. But if you attempted to return a record with that private field to on-chain without re-encrypting or something, the type system prevents it. In one cautionary example, a token’s &lt;code&gt;expire_at&lt;/code&gt; timestamp was private, but the developer passed it into a finalize function (which is on-chain code) and accidentally made it public. Leo’s type system didn’t exactly stop the code, but documentation warns that this leaks information: any distinct &lt;code&gt;expire_at&lt;/code&gt; values become public clues. The fix was to enforce only bounds checks on chain instead of revealing the exact secret.&lt;/p&gt;
  &lt;p id=&quot;AXMQ&quot;&gt;&lt;strong&gt;Parallel with analogies.&lt;/strong&gt; Think of a Leo &lt;code&gt;record&lt;/code&gt; like a sealed envelope (private) containing data (fields). If you open the envelope &lt;em&gt;in a transition&lt;/em&gt;, you can read and use the data (still secretly). If you then drop some paper labeled “sealed” back into the blockchain, you keep it secret. But if you peel off the envelope and reveal even one word on-chain, you’ve “declared” that field public. The type system is watching to make sure you don’t accidentally do that without realizing.&lt;/p&gt;
  &lt;h2 id=&quot;UbB3&quot;&gt;Compile-Time Enforcement of Privacy&lt;/h2&gt;
  &lt;p id=&quot;9sa1&quot;&gt;How exactly does Leo &lt;em&gt;enforce&lt;/em&gt; privacy at compile time? At a high level, the compiler and type checker track the privacy labels as part of type information and enforce a &lt;strong&gt;no-leak policy&lt;/strong&gt;. Let’s break it down:&lt;/p&gt;
  &lt;ol id=&quot;EoeH&quot;&gt;
    &lt;li id=&quot;14M6&quot;&gt;&lt;strong&gt;Expression Typing.&lt;/strong&gt; Every expression is assigned not just a type (e.g. &lt;code&gt;u32&lt;/code&gt;, &lt;code&gt;address&lt;/code&gt;, &lt;code&gt;Record&amp;lt;Token&amp;gt;&lt;/code&gt;, etc.) but also a privacy tag (public or private). For instance, an expression of type &lt;code&gt;u32&lt;/code&gt; might be &lt;code&gt;(private u32)&lt;/code&gt; or &lt;code&gt;(public u32)&lt;/code&gt;. The context (function signature, variable annotation) determines this tag. The type checker propagates these tags through operations:&lt;/li&gt;
    &lt;ul id=&quot;7yS1&quot;&gt;
      &lt;li id=&quot;I0hn&quot;&gt;&lt;strong&gt;Combining values.&lt;/strong&gt; If you add a public and a private integer (&lt;code&gt;a + b&lt;/code&gt; where &lt;code&gt;a&lt;/code&gt; is public, &lt;code&gt;b&lt;/code&gt; is private), the result is private (the logic is still hidden by &lt;code&gt;b&lt;/code&gt;). In general, any operation that involves a private operand yields a private result. (This is similar to security type systems: &lt;code&gt;private + public -&amp;gt; private&lt;/code&gt;.)&lt;/li&gt;
      &lt;li id=&quot;0sDJ&quot;&gt;&lt;strong&gt;Assignments and return.&lt;/strong&gt; Assigning a private value to a variable makes that variable private. Returning a value in a transition causes it to be a private output by default. If you try to bind a private result to a public output, the compiler will error.&lt;/li&gt;
    &lt;/ul&gt;
    &lt;li id=&quot;VSsb&quot;&gt;&lt;strong&gt;Function/Transition Signatures.&lt;/strong&gt; The compiler knows the specified visibility of every input and output. For a transition declared &lt;code&gt;transition foo(public x: u64, y: u64) -&amp;gt; u64&lt;/code&gt;, it treats &lt;code&gt;y&lt;/code&gt; as private (default). If inside &lt;code&gt;foo&lt;/code&gt; you wrote &lt;code&gt;return y;&lt;/code&gt;, that is valid: the function output is private (no qualifier given), so a private &lt;code&gt;y&lt;/code&gt; can flow into it. But if you wrote &lt;code&gt;-&amp;gt; public u64&lt;/code&gt; and still did &lt;code&gt;return y;&lt;/code&gt;, that would be a type error: you can’t return a private value on a public channel.&lt;/li&gt;
    &lt;li id=&quot;tFD4&quot;&gt;&lt;strong&gt;Record Fields.&lt;/strong&gt; When you construct a record value, the visibility tags on fields are enforced. If you declare &lt;code&gt;record Token { pub owner: address, private amount: u128 }&lt;/code&gt;, the compiler will only allow assignments to &lt;code&gt;owner&lt;/code&gt; from public addresses, and to &lt;code&gt;amount&lt;/code&gt; only from private values. The code in examples usually omits qualifiers, but if you did &lt;code&gt;public&lt;/code&gt;, Leo would treat those fields as if you planned to reveal them.&lt;/li&gt;
    &lt;li id=&quot;NBNk&quot;&gt;&lt;strong&gt;Pattern Matching/Destructuring.&lt;/strong&gt; If Leo allows pattern matching or destructuring on enums or Option/Result (like &lt;code&gt;match&lt;/code&gt; or &lt;code&gt;if let&lt;/code&gt;), the privacy of the matched value follows the branch. For instance: &lt;code&gt;let maybe: Option&amp;lt;u32&amp;gt; = compute(); match maybe { Some(v) =&amp;gt; print(v), None =&amp;gt; print(&amp;quot;none&amp;quot;), } &lt;/code&gt; If &lt;code&gt;maybe&lt;/code&gt; was private, then inside the &lt;code&gt;Some(v)&lt;/code&gt; branch, &lt;code&gt;v&lt;/code&gt; is also private (and the match itself runs off-chain). If somehow the language allowed printing &lt;code&gt;v&lt;/code&gt; in a public function, that would be disallowed by type checking (because you’d be printing secret data publicly). (Leo’s actual syntax may differ, but the idea holds.)&lt;/li&gt;
    &lt;li id=&quot;BEK1&quot;&gt;&lt;strong&gt;Cross-Function Calls.&lt;/strong&gt; When calling another function or transition within the same program, Leo checks argument visibilities. A &lt;code&gt;public&lt;/code&gt; formal parameter cannot accept a private argument (type mismatch). Conversely, a private parameter can take either a public or private argument (public data can flow into private context without leaking). If you call an &lt;em&gt;async transition&lt;/em&gt; (which runs off-chain) with a private value, it’s fine; if you call a normal (on-chain) function with a private argument, it will be forced public (because on-chain code only sees public inputs) – Leo will treat it as an implicit leak if you do so. In effect, calling an on-chain function is like exposing any arguments you pass; the type system ensures you passed only public ones or it errors.&lt;/li&gt;
  &lt;/ol&gt;
  &lt;p id=&quot;iGcd&quot;&gt;In practice, the Leo compiler’s &lt;strong&gt;type checker&lt;/strong&gt; implements these rules. It performs type inference (solving for type variables) while simultaneously solving for privacy consistency. If it encounters a potential leak, it produces a compile-time error like “cannot use private expression in public context”. Thus, by the time you get a compiled program, you have formal guarantees:&lt;/p&gt;
  &lt;ul id=&quot;YyOI&quot;&gt;
    &lt;li id=&quot;uOnD&quot;&gt;&lt;em&gt;Non-interference:&lt;/em&gt; Changing the private inputs (in all possible ways) cannot change any public outputs. The type system has already ensured no dependency.&lt;/li&gt;
    &lt;li id=&quot;3Y7N&quot;&gt;&lt;em&gt;Encryption Invariance:&lt;/em&gt; Any variable marked private will never become part of a revealed state by the code you wrote (unless you explicitly re-mark it public).&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;iDLc&quot;&gt;A simple way to see this &lt;strong&gt;Leo effectively extends types with a “privacy bit”&lt;/strong&gt;. Every variable type is &lt;code&gt;(T, vis)&lt;/code&gt; where &lt;code&gt;vis ∈ {public, private}&lt;/code&gt;. Operations propagate the bit with simple lattice rules. Then the checker demands that the &lt;code&gt;vis&lt;/code&gt; of outputs ≥ the &lt;code&gt;vis&lt;/code&gt; of inputs under a lattice where &lt;code&gt;private &amp;gt; public&lt;/code&gt; (private is more restrictive). This is analogous to language-based information-flow type systems used in security.&lt;/p&gt;
  &lt;p id=&quot;ziCu&quot;&gt;In sum, the Leo type system &lt;strong&gt;enforces privacy by construction&lt;/strong&gt;. The compiler itself acts as a privacy auditor. This is fundamentally different from many smart contract languages (or even typical ZK DSLs) where privacy constraints might have to be manually handled. In Leo, “if it compiles, it’s safe” – at least with respect to not revealing secrets.&lt;/p&gt;
  &lt;h2 id=&quot;NJsR&quot;&gt;Polymorphism, Type Inference, and Complex Types&lt;/h2&gt;
  &lt;p id=&quot;BszT&quot;&gt;Leo is not just about basic types – it supports &lt;strong&gt;generics, ADTs, and inference&lt;/strong&gt; that allow very expressive type definitions, all while preserving privacy semantics. Let’s delve into each:&lt;/p&gt;
  &lt;h3 id=&quot;ZpcY&quot;&gt;Generic Types and Traits&lt;/h3&gt;
  &lt;p id=&quot;nT3C&quot;&gt;Leo allows you to write &lt;strong&gt;generic functions and structs&lt;/strong&gt; with type parameters. For example:&lt;/p&gt;
  &lt;pre id=&quot;H0sH&quot;&gt;// A generic identity function
fn id&amp;lt;T&amp;gt;(x: T) -&amp;gt; T { 
    return x;
}

// A struct with a generic field
struct Pair&amp;lt;T&amp;gt; {
    first: T,
    second: T,
}
let p = Pair&amp;lt;int&amp;gt; { first: 3, second: 4 };&lt;/pre&gt;
  &lt;p id=&quot;VRZ2&quot;&gt;This works just like in Rust or C++ templates. The compiler infers or checks that type parameters are used consistently. Leo even supports &lt;strong&gt;type-level integers&lt;/strong&gt; (numeric generics) for arrays, like Noir does, but we won’t detail that here.&lt;/p&gt;
  &lt;p id=&quot;3Kqz&quot;&gt;Leo’s &lt;strong&gt;trait&lt;/strong&gt; system is its way to do ad-hoc polymorphism (interfaces). You define a trait (a collection of method signatures) and then implement it for types. For example&lt;/p&gt;
  &lt;pre id=&quot;z86M&quot;&gt;trait Shape {
    fn area(self) -&amp;gt; f64;
}

struct Circle {
    radius: f64,
}
impl Shape for Circle {
    fn area(self) -&amp;gt; f64 {
        3.14159 * self.radius * self.radius
    }
}

struct Rectangle {
    width: f64,
    height: f64,
}
impl Shape for Rectangle {
    fn area(self) -&amp;gt; f64 {
        self.width * self.height
    }
}

// Now both Circle and Rectangle implement Shape
let c = Circle { radius: 5.0 };
let r = Rectangle { width: 4.0, height: 6.0 };
print(&amp;quot;Circle area: &amp;quot;);
print(c.area());     // dispatches to Circle’s implementation&lt;/pre&gt;
  &lt;p id=&quot;JbfK&quot;&gt;The compiler verifies at compile time that each &lt;code&gt;impl&lt;/code&gt; provides the required method with correct types. Then you can call &lt;code&gt;area()&lt;/code&gt; on any &lt;code&gt;Shape&lt;/code&gt;-typed variable and Leo does static dispatch (monomorphization). This is powerful for polymorphism: you can write code that works for &lt;em&gt;any&lt;/em&gt; type that implements a trait.&lt;/p&gt;
  &lt;p id=&quot;blFt&quot;&gt;All of these generic and trait types still carry public/private labels. You could even have a &lt;code&gt;trait Foo&amp;lt;T&amp;gt;&lt;/code&gt; or a &lt;code&gt;struct Wrapper&amp;lt;T&amp;gt;&lt;/code&gt; where &lt;code&gt;T&lt;/code&gt; itself might be public or private. Leo’s type system composes these: if &lt;code&gt;T&lt;/code&gt; is private, then &lt;code&gt;Wrapper&amp;lt;T&amp;gt;&lt;/code&gt; values are private, etc.&lt;/p&gt;
  &lt;h3 id=&quot;8vQy&quot;&gt;Type Inference&lt;/h3&gt;
  &lt;p id=&quot;75do&quot;&gt;Leo infers types where possible. The developer doesn’t have to annotate every variable. For example:&lt;/p&gt;
  &lt;pre id=&quot;SVIh&quot;&gt;let a = 10u8;          // a is inferred u8
let b = a + 5u8;       // b is u8
let c: u32 = a;        // error! need an explicit cast to u32
let d = (a as u32) + 100u32;  // d is u32&lt;/pre&gt;
  &lt;p id=&quot;7NAo&quot;&gt;For function return types or struct fields, you typically annotate explicitly (like in most typed languages). But local &lt;code&gt;let&lt;/code&gt; bindings and lambdas (if present) benefit from inference. This makes code cleaner without losing safety. The Leo compiler’s formal description of type checking/inference notes that it solves missing types uniquely or flags ambiguity.&lt;/p&gt;
  &lt;p id=&quot;FihO&quot;&gt;Type inference also works with privacy qualifiers in a logical way. For instance, if you write &lt;code&gt;let x = amount + 5u32;&lt;/code&gt; and if &lt;code&gt;amount&lt;/code&gt; was private (unqualified in a transition), then &lt;code&gt;x&lt;/code&gt; is inferred as private too, without you having to write &lt;code&gt;private let x&lt;/code&gt;. The compiler just tracks the underlying privacy label and flows it through expressions.&lt;/p&gt;
  &lt;h3 id=&quot;ITUB&quot;&gt;Examples of Complex Types&lt;/h3&gt;
  &lt;p id=&quot;acn3&quot;&gt;To illustrate how these features work together, consider a hypothetical &lt;code&gt;Option&amp;lt;Result&amp;lt;T, E&amp;gt;&amp;gt;&lt;/code&gt; example, combining generics, sum types, and privacy&lt;/p&gt;
  &lt;pre id=&quot;zkRG&quot;&gt;// A function that might fail or be absent
fn compute(secret: private u64) -&amp;gt; Option&amp;lt;Result&amp;lt;u64, str&amp;gt;&amp;gt; {
    if secret == 0u64 {
        return Some(Err(&amp;quot;Zero is not allowed&amp;quot;));
    } else if secret &amp;lt; 10u64 {
        return None;
    } else {
        return Some(Ok(secret * 2u64));
    }
}

// Calling the function
let result = compute(private_input); // result has type Option&amp;lt;Result&amp;lt;u64,str&amp;gt;&amp;gt;
match result {
    Some(Ok(v)) =&amp;gt; print(v),         // v is private u64
    Some(Err(msg)) =&amp;gt; print(msg),    // msg is a str (private by default)
    None =&amp;gt; print(&amp;quot;nothing&amp;quot;),
}&lt;/pre&gt;
  &lt;p id=&quot;U0RR&quot;&gt;Here &lt;code&gt;compute&lt;/code&gt; takes a private &lt;code&gt;u64&lt;/code&gt; (we declared &lt;code&gt;private secret: u64&lt;/code&gt;) and returns &lt;code&gt;Option&amp;lt;Result&amp;lt;u64, str&amp;gt;&amp;gt;&lt;/code&gt;. Inside, it uses an &lt;code&gt;if&lt;/code&gt; to return either &lt;code&gt;None&lt;/code&gt; or &lt;code&gt;Some(Ok(..))&lt;/code&gt; or &lt;code&gt;Some(Err(..))&lt;/code&gt;. The type checker ensures each branch matches the declared return type. When we get &lt;code&gt;result&lt;/code&gt;, pattern matching on it (&lt;code&gt;Some(v)&lt;/code&gt;) gives &lt;code&gt;v&lt;/code&gt; the inner type and privacy. Notice we didn’t annotate &lt;code&gt;print&lt;/code&gt; calls as requiring anything – but if &lt;code&gt;compute&lt;/code&gt; were a transition input or output, the compiler would check that printing a private value is only allowed in off-chain code (transitions or internal functions), not in an on-chain finalize function.&lt;/p&gt;
  &lt;p id=&quot;4wbW&quot;&gt;This example uses generics (&lt;code&gt;Option&lt;/code&gt; and &lt;code&gt;Result&lt;/code&gt; with type parameters &lt;code&gt;u64&lt;/code&gt; and &lt;code&gt;str&lt;/code&gt;), a trait-like pattern (&lt;code&gt;Err&lt;/code&gt;/&lt;code&gt;Ok&lt;/code&gt; construction, which in Leo is an intrinsic type), and type inference for local bindings like &lt;code&gt;result&lt;/code&gt;. The Leo compiler ensures at compile time that the complex type is consistent and that privacy is respected (e.g. the private &lt;code&gt;v&lt;/code&gt; is never forcibly revealed). In fact, in real Aleo/Leo code one rarely hardcodes &lt;code&gt;Some&lt;/code&gt;/&lt;code&gt;None&lt;/code&gt;; it’s usually library-provided, but the concept is the same.&lt;/p&gt;
  &lt;p id=&quot;zh7f&quot;&gt;&lt;strong&gt;“Aha!” insight:&lt;/strong&gt; Leo’s type system is as expressive as Rust or Swift when it comes to defining complex types and interfaces. The twist is that every such type is also &lt;em&gt;either public or private&lt;/em&gt;. You effectively have a &lt;strong&gt;two-dimensional&lt;/strong&gt; type: one dimension is the usual algebraic structure, and the other is the privacy lattice. The compiler enforces both dimensions simultaneously.&lt;/p&gt;
  &lt;h2 id=&quot;HBAq&quot;&gt;Interaction with Blockchain State and External Calls&lt;/h2&gt;
  &lt;p id=&quot;zkOt&quot;&gt;Leo’s type system must account for the split between off-chain computation (transitions) and on-chain state changes. There are two main state mechanisms: &lt;strong&gt;records (UTXOs)&lt;/strong&gt; and &lt;strong&gt;mappings&lt;/strong&gt; (key-value store). Each interacts with types differently.&lt;/p&gt;
  &lt;h3 id=&quot;aV5Q&quot;&gt;Records (UTXOs)&lt;/h3&gt;
  &lt;p id=&quot;wORz&quot;&gt;A &lt;em&gt;record&lt;/em&gt; type encapsulates private state in the ledger. For example:&lt;/p&gt;
  &lt;pre id=&quot;SAfZ&quot;&gt;transition createToken(public owner: address, public amount: u64) -&amp;gt; Token {
    let token: Token = Token { owner: owner, amount: amount };
    return token;
}&lt;/pre&gt;
  &lt;p id=&quot;QQ4o&quot;&gt;This &lt;code&gt;createToken&lt;/code&gt; transition makes a new &lt;code&gt;Token&lt;/code&gt; record with public fields (we marked both &lt;code&gt;owner&lt;/code&gt; and &lt;code&gt;amount&lt;/code&gt; public here). In reality, you rarely do that – usually owner and amount are private so only the prover knows them. When a transition returns a &lt;code&gt;Token&lt;/code&gt; record (using the record name as return type), Leo creates a new UTXO on-chain, encrypted with the program’s public key.&lt;/p&gt;
  &lt;p id=&quot;lMfW&quot;&gt;When &lt;strong&gt;consuming&lt;/strong&gt; a record as input, the transition must list it as a parameter, e.g. &lt;code&gt;transition spend(my_token: Token) -&amp;gt; u64&lt;/code&gt;. Leo ensures that the signer of the transaction is the record’s owner (the transition can only execute if authorized). Importantly: if you consume a record, all its private fields are &lt;em&gt;consumed&lt;/em&gt; off-chain and lost from on-chain view (standard UTXO semantics). The type system ensures you don’t mix up types: you can only consume a &lt;code&gt;Token&lt;/code&gt; where a &lt;code&gt;Token&lt;/code&gt; is expected.&lt;/p&gt;
  &lt;p id=&quot;DoK9&quot;&gt;A &lt;strong&gt;key nuance&lt;/strong&gt; (and gotcha) is how cross-program records are handled. In Leo, record types are &lt;em&gt;scoped&lt;/em&gt; by program. For instance, if program &lt;code&gt;A.aleo&lt;/code&gt; defines &lt;code&gt;record Credits { ... }&lt;/code&gt; and program &lt;code&gt;B.aleo&lt;/code&gt; imports it, we write &lt;code&gt;A.aleo/Credits&lt;/code&gt; to refer to that type. The Leo compiler tracks the origin. As the ZKSecurity blog notes, if program B tries to consume &lt;code&gt;A.aleo/Credits&lt;/code&gt; directly in its own transition, it &lt;em&gt;won’t work&lt;/em&gt; – B doesn’t have the private key of A to authorize spending. The correct way is for B to call A’s transition (e.g. &lt;code&gt;A.aleo/transfer_private&lt;/code&gt;) to let A burn the record. Example from ZKSecurity:&lt;/p&gt;
  &lt;pre id=&quot;cWUx&quot;&gt;// In example_program5.aleo (program B)
transition burn(credit: credits.aleo/Credits) -&amp;gt; BurnerCertificate {
    // ... build new certificate record ...
    // Call external program A to burn the credit:
    credits.aleo/transfer_private(credit, ZERO_ADDRESS, credit.amount);
    return certificate;
}&lt;/pre&gt;
  &lt;p id=&quot;oOYs&quot;&gt;Here &lt;code&gt;credits.aleo/transfer_private&lt;/code&gt; is an &lt;em&gt;external&lt;/em&gt; transition call. Leo’s type system allows this call syntax (&lt;code&gt;program_name/transition_name&lt;/code&gt;). It checks that &lt;code&gt;credit&lt;/code&gt; is of type &lt;code&gt;credits.aleo/Credits&lt;/code&gt;, and then dispatches the call. Importantly, because &lt;code&gt;transfer_private&lt;/code&gt; is in program A (the credits program), it will succeed in burning the record: &lt;em&gt;“The record will be burned because the record and function are defined in the same program”&lt;/em&gt;. Meanwhile, &lt;code&gt;example_program5.aleo&lt;/code&gt; never erroneously consumed the record itself – it asked A. So Leo’s type system, combined with the program scoping, prevented a forbidden action (B consuming A’s record) by design.&lt;/p&gt;
  &lt;p id=&quot;UDrt&quot;&gt;Finally, note that the ZoeKSecurity article warns: &lt;strong&gt;never transfer a record to a program address&lt;/strong&gt; because a program has no private key to use it. The type system doesn’t stop you from sending a record to a program parameter (it’s syntactically allowed), but the semantics are that the record is effectively lost (can’t be spent). This is a higher-level caution beyond typing, but it arises from the key structure. Developers must be aware that &lt;em&gt;only EOAs (externally owned accounts)&lt;/em&gt; or the program’s own address can be owners of records.&lt;/p&gt;
  &lt;h3 id=&quot;Tb3d&quot;&gt;Mappings (Public State)&lt;/h3&gt;
  &lt;p id=&quot;TMgu&quot;&gt;In addition to UTXO-style records, Leo supports &lt;strong&gt;mappings&lt;/strong&gt; as an account-like key-value store for public data. For example:&lt;/p&gt;
  &lt;pre id=&quot;10N1&quot;&gt;mapping balances: address =&amp;gt; u64;&lt;/pre&gt;
  &lt;p id=&quot;cFPt&quot;&gt;This declares a public mapping named &lt;code&gt;balances&lt;/code&gt;. The type &lt;code&gt;u64&lt;/code&gt; here is effectively &lt;em&gt;always public&lt;/em&gt; because mappings are on-chain storage that anyone can query&lt;a href=&quot;https://www.aleo.org/post/public-vs-private-state-aleo/#:~:text=Storing%20Public%20State%20via%20Mapping&quot; target=&quot;_blank&quot;&gt;aleo.org&lt;/a&gt;. In a finalize (on-chain) function, one can write:&lt;/p&gt;
  &lt;pre id=&quot;bVde&quot;&gt;finalize update_balance(public addr: address, public amt: u64) {
    let current: u64 = Mapping::get_or_use(balances, addr, 0u64);
    Mapping::set(balances, addr, current + amt);
}&lt;/pre&gt;
  &lt;p id=&quot;LHFT&quot;&gt;Here &lt;code&gt;Mapping::get_or_use&lt;/code&gt; and &lt;code&gt;Mapping::set&lt;/code&gt; deal with public values only. Leo enforces that mapping operations live in &lt;em&gt;public on-chain code&lt;/em&gt; (finalize functions), not in private off-chain transitions. A transition could, for example, pass data to a finalize function to update a mapping (much like the &lt;code&gt;mint_public&lt;/code&gt; example in Birgitta’s post). If one tried to use &lt;code&gt;Mapping::get&lt;/code&gt; in a private transition, Leo would flag it as invalid: private transitions cannot directly read or write on-chain global state.&lt;/p&gt;
  &lt;p id=&quot;pFhY&quot;&gt;In summary, the &lt;strong&gt;Nuance:&lt;/strong&gt; &lt;em&gt;records = private state (encrypted, UTXO-like); mappings = public state (open K/V store)&lt;/em&gt;. Leo’s type system and semantics ensure each is used appropriately. Transitions (private off-chain) produce/consume records, while finalize/public functions modify mappings.&lt;/p&gt;
  &lt;h2 id=&quot;380s&quot;&gt;Privacy Enforcement in Practice: Real-World Scenarios&lt;/h2&gt;
  &lt;p id=&quot;xUEu&quot;&gt;Let’s step back and see why Leo’s type system really matters for developers. What problems does it solve?&lt;/p&gt;
  &lt;ul id=&quot;t5kk&quot;&gt;
    &lt;li id=&quot;h2Eq&quot;&gt;&lt;strong&gt;Accidentally leaking secrets.&lt;/strong&gt; In many ZK languages, it’s all too easy to print a secret or send it in a public transaction by mistake. Leo’s type checker stops you. For instance, if you try &lt;code&gt;finalize do_something(private secret: u64) { return secret; }&lt;/code&gt; the compiler knows you’re trying to reveal a private parameter as output and will error. The blog article “Aleo’s Instruction to Prevent Security Flaws” found that enforcing integer overflow also helps avoid sneaky leaks. The type system similarly prevents mismatched privacy.&lt;/li&gt;
    &lt;li id=&quot;HogW&quot;&gt;&lt;strong&gt;Mis-managing UTXOs.&lt;/strong&gt; When dealing with blockchain tokens and UTXOs, developers often mix up which program owns which record. Leo’s types encode the program ID in the type (e.g. &lt;code&gt;credits.aleo/Credits&lt;/code&gt;), and the checker will not let you assume it’s a local type. This stopped a class of bugs: if program B tried to consume program A’s record, the compiler or runtime would catch that. The developer had to explicitly call A’s function instead. In effect, the type system serves as a &lt;strong&gt;cross-contract safety net&lt;/strong&gt;.&lt;/li&gt;
    &lt;li id=&quot;zRty&quot;&gt;&lt;strong&gt;Combining private and public logic.&lt;/strong&gt; Some contract logic needs to do both (e.g. compute a private proof then update a public ledger). Leo’s split between transitions and finalize, combined with the type system, makes it clear which values are which. For example, if you have a private puzzle solution, the solver transition can hold it privately, then a finalize function can update public state with a token, &lt;em&gt;without revealing the solution&lt;/em&gt;. The type system forces you to handle the values correctly.&lt;/li&gt;
    &lt;li id=&quot;f2Wl&quot;&gt;&lt;strong&gt;Formal reasoning and proofs.&lt;/strong&gt; Because Leo’s types guarantee privacy constraints, it becomes easier to formally verify or reason about contracts. We know that the compiler already checked non-interference. Combined with Aleo’s aim of formal verification, one can treat privacy as an invariant ensured by the type system. This reduces the attack surface: you only have to worry about logic correctness (is the proof statement true?) rather than secret leaks.&lt;/li&gt;
    &lt;li id=&quot;YcLv&quot;&gt;&lt;strong&gt;Reusability and libraries.&lt;/strong&gt; In languages without privacy types, every function must be either written for secret or for public data, leading to duplicate code. In Leo, a single function can be generic: the same code could be used in a public or private context by just changing qualifiers. For instance, a sorting function that takes a list of fields could operate on private data with no code change. This makes libraries naturally privacy-aware.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;8UE0&quot;&gt;Comparatively, consider other ZK DSLs:&lt;/p&gt;
  &lt;ul id=&quot;L9Kf&quot;&gt;
    &lt;li id=&quot;iT3r&quot;&gt;&lt;strong&gt;ZoKrates.&lt;/strong&gt; A widely-used ZKP toolkit, ZoKrates is statically typed but has no built-in notion of public vs private types in the language; instead, the final proof statement lists which variables are public inputs. This is error-prone: you might forget to mark something public and fail the protocol. With Leo’s approach, the privacy label is part of the source code’s type, so it’s far less likely to be inconsistent. ZoKrates also lacks generics and ADTs – it’s more like a simple expression language for circuits. Leo’s type system is much richer, enabling more expressive and maintainable code.&lt;/li&gt;
    &lt;li id=&quot;RNSK&quot;&gt;&lt;strong&gt;Circom.&lt;/strong&gt; A circuit DSL embedded in JavaScript, Circom has virtually no static typing (it checks the circuit graph, but JavaScript parts are dynamic). Variables are just wires or arrays of wires. There is no &lt;code&gt;private/public&lt;/code&gt; keyword – all inputs default to private (witnesses) unless listed as public inputs in the template. There is no compile-time enforcement of privacy at the language level. Leo’s type system is a big advance: it moves error detection from debugging time into compilation. Also, Circom has no ADTs or high-level control structures; everything is manual. Leo’s types (records, structs, etc.) let you model problems at a higher level.&lt;/li&gt;
    &lt;li id=&quot;uoCr&quot;&gt;&lt;strong&gt;Noir.&lt;/strong&gt; Aztec’s new ZK language is actually quite similar to Leo in some respects. Noir has generics, traits, structs, and enums too (as seen in its docs), and it distinguishes private/public. However, Leo predates Noir and pushes even more ergonomics around privacy. Both are statically typed and suitable for similar tasks, but Leo’s design is explicitly for SnarkVM on Aleo. One difference: Noir currently requires marking functions as &lt;code&gt;unconstrained&lt;/code&gt; for off-chain code, whereas Leo uses &lt;code&gt;async transition&lt;/code&gt; vs &lt;code&gt;finalize&lt;/code&gt;. The core idea (types tracking privacy) is shared, but Leo’s syntax integrates it deeply (with built-in record types, etc.).&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;uWSa&quot;&gt;The overall message: &lt;strong&gt;Leo’s type system automates privacy discipline&lt;/strong&gt;. It prevents classes of mistakes that would be easy to make otherwise. As the official Aleo documentation puts it, Leo is “statically typed, which can detect type errors at compile time and reduce runtime errors”. Here, “type errors” include privacy mis-labeling as well as simple mismatches.&lt;/p&gt;
  &lt;h2 id=&quot;3945&quot;&gt;Comparison with Other ZK Languages&lt;/h2&gt;
  &lt;p id=&quot;i0Dp&quot;&gt;We briefly highlight how Leo’s approach stands out:&lt;/p&gt;
  &lt;ul id=&quot;ppD2&quot;&gt;
    &lt;li id=&quot;nFyz&quot;&gt;&lt;strong&gt;ZoKrates.&lt;/strong&gt; Everything is a field (no &lt;code&gt;public&lt;/code&gt; keyword). You &lt;em&gt;later&lt;/em&gt; tell the system which outputs/inputs are public. No static check means you could accidentally omit a public tag. No generics/ADTs; you deal with raw arrays and arithmetic circuits. Leo’s rich type system makes code safer and more readable.&lt;/li&gt;
    &lt;li id=&quot;UhCb&quot;&gt;&lt;strong&gt;Circom.&lt;/strong&gt; Circom templates operate on “signals”. You wire up circuits, but there is minimal type checking (basically just checking input/output counts). The privacy of each signal depends on context and how you compile the circuit. Leo instead bakes the distinction into language types, catching missteps at compile time.&lt;/li&gt;
    &lt;li id=&quot;5hi2&quot;&gt;&lt;strong&gt;Noir.&lt;/strong&gt; Noir has many similar high-level features (structs, enums, generics, traits). Both languages emphasize privacy by design. A key difference is ecosystem: Leo is tied to Aleo/SnarkVM, while Noir targets ACIR proving backends. The type philosophy, however, is aligned: functions can be marked &lt;code&gt;async fn&lt;/code&gt; (ZK) vs &lt;code&gt;fn&lt;/code&gt; (constrained/public). Leo’s syntax (&lt;code&gt;public x: T&lt;/code&gt;) is very explicit, arguably even clearer. Also, Leo’s integrated “record” type for UTXO is a domain-specific innovation – Noir currently has only struct types and doesn’t have a first-class UTXO construct with owner.&lt;/li&gt;
    &lt;li id=&quot;XpS8&quot;&gt;&lt;strong&gt;Others (Plonk/Stencil-based frameworks).&lt;/strong&gt; Some newer systems allow writing circuits in languages like Rust or TypeScript (via plugins). These often lack a built-in privacy type system and rely on library patterns. Leo’s language-level guarantees are much higher assurance: it’s not just a library, it’s built into the compiler.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h2 id=&quot;C1Ir&quot;&gt;Summary and Insights&lt;/h2&gt;
  &lt;p id=&quot;x8XE&quot;&gt;Leo’s type system is &lt;strong&gt;the secret weapon of privacy-preserving development&lt;/strong&gt;. By raising privacy to a type, Leo ensures that &lt;em&gt;if your program compiles, it already passed a privacy audit&lt;/em&gt;. This is not a panacea (one still must prove correct relations), but it eliminates a whole class of developer errors. As the guides emphasize, Leo is not just “like Rust” – it is &lt;em&gt;Rust plus privacy&lt;/em&gt;. You get algebraic types and memory safety, plus encryption by default.&lt;/p&gt;
  &lt;p id=&quot;vuBZ&quot;&gt;Some &lt;strong&gt;key analogies&lt;/strong&gt;:&lt;/p&gt;
  &lt;ul id=&quot;xZoQ&quot;&gt;
    &lt;li id=&quot;mBYH&quot;&gt;Think of &lt;code&gt;private&lt;/code&gt; as “inside a sealed vault” and &lt;code&gt;public&lt;/code&gt; as “on a bulletin board”. Leo’s type checker is the guard that won’t let you pin a secret note on the board unless you take it out of the vault explicitly (which you can only do if you mark it public).&lt;/li&gt;
    &lt;li id=&quot;Dd35&quot;&gt;Using generics/traits in Leo is like using templates in C++ or generics in Rust – code is abstract and reusable. The “aha” moment is that you can write one algorithm (say, a safe integer operation) and it applies to both public and private contexts without change. The privacy tag is orthogonal to your generic logic.&lt;/li&gt;
    &lt;li id=&quot;un5e&quot;&gt;If you imagine writing a program without such a type system, it would be like writing code without any variable names – you might confuse which piece is secret. Leo’s types label everything, so clarity and safety go up.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;LrDK&quot;&gt;In practice, every Leo developer soon appreciates the &lt;em&gt;clarity&lt;/em&gt; of knowing what’s secret at a glance. The syntax is verbose (you &lt;em&gt;see&lt;/em&gt; &lt;code&gt;public&lt;/code&gt; or not on every signature), but that verbosity is a feature, not a bug. It embeds best practice into the language. (You’ll never “forget” a privacy keyword if the compiler demands it.)&lt;/p&gt;
  &lt;p id=&quot;ak4E&quot;&gt;&lt;strong&gt;Write by alexanderblv for the Aleo, September 2025 &lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;KI1X&quot;&gt;&lt;strong&gt;&lt;a href=&quot;https://x.com/alexander_blv&quot; target=&quot;_blank&quot;&gt;x.com/alexander_blv&lt;/a&gt; &lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;ZD3E&quot;&gt;&lt;strong&gt;ERC20 - 0x1e1Aa06ff5DC84482be94a216483f946D0bC67e7&lt;/strong&gt;&lt;/p&gt;

</content></entry><entry><id>alexanderblv:LeoCompiler</id><link rel="alternate" type="text/html" href="https://teletype.in/@alexanderblv/LeoCompiler?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=alexanderblv"></link><title>Leo Compiler. From Source Code to Arithmetic Circuits</title><published>2025-09-29T09:15:18.234Z</published><updated>2025-09-29T09:15:18.234Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img1.teletype.in/files/c2/46/c2461235-1ac5-4cdb-9a43-0bedca857718.png"></media:thumbnail><summary type="html">&lt;img src=&quot;https://img2.teletype.in/files/51/17/51172f1b-a038-46ac-bce3-0d3519fb9372.png&quot;&gt;Leo is a Rust-like language for zero-knowledge applications. Its compiler not only produces efficient R1CS circuits but also formally verifies every step, making it unique among ZK DSLs. With built-in testing, package management, and Aleo’s universal SNARK support, Leo lets developers write high-level code that compiles into provably correct circuits.</summary><content type="html">
  &lt;p id=&quot;p5r5&quot;&gt;Aleo’s Leo is a high-level, Rust-like programming language designed for writing provable programs with zero-knowledge succinct proofs. Unlike traditional compilers, the Leo compiler not only emits R1CS circuits but also formally verifies each step: at each stage it generates machine-checkable proofs (via ACL2) of correct transformation. This makes Leo unique among ZK languages, and the first known language to include a testing framework, package manager, remote compiler and theorem prover for general-purpose ZK applications. In practice, developers write familiar constructs (functions, loops, structs, etc.) in Leo, and the compiler lowers them through multiple phases into efficient Rank-1 Constraint Systems (R1CS) for proving. We examine these compiler stages in detail, show how high-level Leo code turns into circuits, and compare Leo’s approach to that of Circom and ZoKrates.&lt;/p&gt;
  &lt;h2 id=&quot;3gIH&quot;&gt;Compilation Pipeline Overview&lt;/h2&gt;
  &lt;p id=&quot;bYdF&quot;&gt;The Leo compiler pipeline is a sequence of well-defined phases that gradually transform Leo source text into an R1CS circuit. At a high level it works as follows: the &lt;strong&gt;parser&lt;/strong&gt; reads Leo code (using a PEG grammar) and produces a &lt;strong&gt;Grammar AST&lt;/strong&gt;. This is immediately converted into a cleaner &lt;strong&gt;AST&lt;/strong&gt; (removing punctuation, comments, etc.) The AST is then transformed into an &lt;strong&gt;Abstract Semantic Graph (ASG)&lt;/strong&gt;, enriching nodes with context (scopes, types, parent functions) to support further analysis. Next, a &lt;strong&gt;canonicalization&lt;/strong&gt; pass simplifies syntax (e.g. resolving aliases and ensuring a unique representation of types). Then the &lt;strong&gt;type-checker and inference&lt;/strong&gt; engine assigns explicit types to all variables (in Leo every program must have fully specified types before R1CS).&lt;/p&gt;
  &lt;p id=&quot;G6bO&quot;&gt;In summary, the Leo pipeline flows &lt;strong&gt;Source Code → (Parsing) → Grammar AST → (AST conversion) → AST → (ASG conversion) → ASG → (Canonicalization) → ASG → (Type Checking) → ASG → (Optimizations) → ASG → (Circuit Synthesis) → R1CS&lt;/strong&gt;. (Each “→” represents a transformation phase, with intermediate artifacts.) In practice this means Leo code is first validated against the syntax and basic constraints (parsing, AST, ASG), then semantically normalized, then optimized, and finally compiled into constraints. &lt;/p&gt;
  &lt;h2 id=&quot;HDtb&quot;&gt;Parsing and AST Conversion&lt;/h2&gt;
  &lt;p id=&quot;mMo7&quot;&gt;&lt;strong&gt;Parsing.&lt;/strong&gt; The compiler begins by reading Leo code text and tokenizing it according to a &lt;strong&gt;parsing expression grammar (PEG)&lt;/strong&gt;. Leo’s grammar is unambiguous: if a file parses, it has a unique parse tree. Lexical and syntactic errors are detected immediately by the PEG parser. (Leo’s formal development even includes a verified ABNF grammar to prove correct parsing.) This phase outputs a &lt;em&gt;Grammar AST&lt;/em&gt; capturing all syntactic elements (including punctuation, keywords, etc.).&lt;/p&gt;
  &lt;p id=&quot;C1I6&quot;&gt;&lt;strong&gt;AST conversion.&lt;/strong&gt; Immediately after parsing, Leo converts the grammar AST into a clean &lt;strong&gt;Abstract Syntax Tree (AST)&lt;/strong&gt;. The AST strips out superfluous nodes (e.g. semicolons, raw syntax tokens, comments) and restructures the parse tree into higher-level nodes (e.g. combine declaration tokens into one &lt;code&gt;Struct&lt;/code&gt; node). For example, parsing the declaration&lt;/p&gt;
  &lt;pre id=&quot;MAk6&quot;&gt;function add(a: u32, b: u32) -&amp;gt; u32 {
    return a + b;
}&lt;/pre&gt;
  &lt;p id=&quot;TkQY&quot;&gt;yields a Grammar AST with separate tokens for &lt;code&gt;function&lt;/code&gt;, identifier, &lt;code&gt;(&lt;/code&gt;, &lt;code&gt;)&lt;/code&gt;, etc. The AST conversion would then produce a node like &lt;code&gt;FunctionDecl(name=&amp;quot;add&amp;quot;, params=[(&amp;quot;a&amp;quot;,u32),(&amp;quot;b&amp;quot;,u32)], returnType=u32, body=Block(...))&lt;/code&gt;. No semantic checks are done here; the goal is simply to reorganize the tree. The compiler also sets up an error-reporting framework at this stage (annotating nodes with source spans).&lt;/p&gt;
  &lt;p id=&quot;Bsu2&quot;&gt;At the end of parsing and AST conversion, we have an AST that exactly represents the program’s structure. For instance, the AST for a simple code snippet might look like:&lt;/p&gt;
  &lt;pre id=&quot;ygXo&quot;&gt;Program(
  Transition(name=&amp;quot;place_bid&amp;quot;, params=[(&amp;quot;bidder&amp;quot;,address),(&amp;quot;amount&amp;quot;,u64)], returnType=Void,
    Block([
      LetStmt(var=&amp;quot;highest_bid&amp;quot;, type=u64, value=&amp;lt;expr&amp;gt;),
      IfStmt(cond=&amp;lt;expr&amp;gt;, then=Block([...]), else=Block([...])),
      ReturnStmt(expr=&amp;quot;highest_bid&amp;quot;)
    ])
  )
)&lt;/pre&gt;
  &lt;p id=&quot;BWi5&quot;&gt;Each node records identifiers, literal values, and nested blocks. This AST is still textual (with high-level constructs like &lt;code&gt;IfStmt&lt;/code&gt;, &lt;code&gt;ForLoop&lt;/code&gt;, etc.).&lt;/p&gt;
  &lt;h2 id=&quot;QnWy&quot;&gt;ASG Conversion. Semantic Graph&lt;/h2&gt;
  &lt;p id=&quot;o7HU&quot;&gt;&lt;strong&gt;ASG conversion.&lt;/strong&gt; Next, the compiler transforms the AST into an &lt;strong&gt;Abstract Semantic Graph (ASG)&lt;/strong&gt;. Unlike the tree-shaped AST, an ASG is a &lt;em&gt;graph&lt;/em&gt; where nodes are connected by semantic relationships. In practice, this means linking each variable and function call to its declaration, typing each literal, and annotating scopes and types on every node. For example, each occurrence of a variable &lt;code&gt;x&lt;/code&gt; in the ASG points back to the single &lt;code&gt;LetStmt(var=&amp;quot;x&amp;quot;, ...)&lt;/code&gt; that defines it. Each expression node in the ASG carries type information (if known) and a pointer to its parent function and circuit. This richer structure makes it much easier to analyze semantics: the ASG can detect undefined variables, duplicate declarations, or attempts to mutate immutable variables.&lt;/p&gt;
  &lt;p id=&quot;1P1h&quot;&gt;Concretely, the AST-to-ASG pass might convert the tree above into something like:&lt;/p&gt;
  &lt;pre id=&quot;xoHW&quot;&gt;LetDecl(name=&amp;quot;highest_bid&amp;quot;, type=u64, value=Call(func=&amp;quot;max&amp;quot;, args=[Var(&amp;quot;a&amp;quot;),Var(&amp;quot;b&amp;quot;)]),
        children=[], parent=Transition(&amp;quot;place_bid&amp;quot;))
If(cond=BinOp(&amp;quot;&amp;gt;&amp;quot;, Var(&amp;quot;a&amp;quot;),Var(&amp;quot;b&amp;quot;)),
   then=Block([ ... ]), else=Block([ ... ]),
   children=[Var(&amp;quot;a&amp;quot;),Var(&amp;quot;b&amp;quot;)],
   parent=Transition(&amp;quot;place_bid&amp;quot;))&lt;/pre&gt;
  &lt;p id=&quot;mmZJ&quot;&gt;where each node has links to its children and context. Crucially, the ASG &lt;em&gt;can be converted back to an AST&lt;/em&gt;, so that the compiler can serialize it for proof-checking. But the ASG itself is more powerful: it supports complex constructs (cycles via variables referencing definitions, context from parent functions, etc.) that a tree cannot. Errors found during AST→ASG conversion (such as unknown identifiers or illegal mutations) immediately abort compilation.&lt;/p&gt;
  &lt;h2 id=&quot;xbce&quot;&gt;Canonicalization&lt;/h2&gt;
  &lt;p id=&quot;0u6x&quot;&gt;After building the ASG, Leo performs &lt;strong&gt;canonicalization&lt;/strong&gt; to simplify syntactic forms. This phase enforces a normalized representation of types and operations so that later phases don’t have to worry about multiple equivalent syntaxes. For example, canonicalization might (depending on details in Section 3.3.1 of the whitepaper) ensure that all integer types are represented in a single form, or that certain syntactic sugar (like implicit casts) is removed. Because all types are now in canonical form, the type-checker can simply compare type nodes by equality.&lt;/p&gt;
  &lt;p id=&quot;WeUI&quot;&gt;As an illustration, suppose the AST had a custom type alias or a shorthand notation; canonicalization would replace these with the fully expanded core types. Or if Leo had any deprecated syntax (the grammar might flag it earlier), canonicalization could rewrite it to the newer form. In practice this pass is relatively straightforward and mostly bookkeeping. Its main role is to prepare for type inference and to simplify reasoning (both for the compiler and for formal proofs). (The ACL2 formalization of Leo includes a precise definition of canonicalization, and the compiler emits a proof that it matches the specification)&lt;/p&gt;
  &lt;h2 id=&quot;V5DL&quot;&gt;Type Checking and Inference&lt;/h2&gt;
  &lt;p id=&quot;anAa&quot;&gt;Next comes &lt;strong&gt;type checking/inference&lt;/strong&gt;. Leo is statically typed: every variable and expression must have a type known at compile time. The compiler verifies that operations are applied to compatible types (e.g. adding two &lt;code&gt;u64&lt;/code&gt; is allowed, adding a &lt;code&gt;u64&lt;/code&gt; and a &lt;code&gt;bool&lt;/code&gt; is not). Leo supports implicit typing in some situations, so the compiler infers types where they are omitted. For example, in a function return statement or a constant declaration, if the type is not explicitly written, it is inferred from context (function signature, constructor types, etc.). By the end of this phase, every variable and expression in the ASG has an explicit type node attached. If any type errors remain (unified types fail to match, missing type info, etc.), compilation fails with an error message telling the developer where to insert an explicit type.&lt;/p&gt;
  &lt;p id=&quot;NOYO&quot;&gt;A key guarantee is: &lt;strong&gt;before generating R1CS, all types are fully explicit and checked&lt;/strong&gt;. This matches Section 3.3.2 of the Leo specification. For example, if a programmer writes &lt;code&gt;let x = a + b;&lt;/code&gt; without specifying &lt;code&gt;: u32&lt;/code&gt;, the compiler will infer that type from &lt;code&gt;a&lt;/code&gt; and &lt;code&gt;b&lt;/code&gt;. If &lt;code&gt;a&lt;/code&gt; and &lt;code&gt;b&lt;/code&gt; were of type &lt;code&gt;u32&lt;/code&gt;, &lt;code&gt;x&lt;/code&gt; is inferred &lt;code&gt;u32&lt;/code&gt;. If &lt;code&gt;a&lt;/code&gt; and &lt;code&gt;b&lt;/code&gt; had mismatched types, a compile-time error is raised. The compiler emits a proof that the resulting AST is a correct type-inference solution to the input AST.&lt;/p&gt;
  &lt;h2 id=&quot;1jfJ&quot;&gt;Optimizations. Folding, Inlining, Unrolling&lt;/h2&gt;
  &lt;p id=&quot;xnBi&quot;&gt;With types resolved, Leo performs high-level &lt;strong&gt;optimizations&lt;/strong&gt; to simplify the program before circuit generation. The three main optimizations are:&lt;/p&gt;
  &lt;ul id=&quot;vzse&quot;&gt;
    &lt;li id=&quot;ZKca&quot;&gt;&lt;strong&gt;Constant folding and propagation:&lt;/strong&gt; Any expression whose operands are all constant values is evaluated at compile time. For instance, &lt;code&gt;let y = 3u8 + 5u8;&lt;/code&gt; becomes &lt;code&gt;let y = 8u8;&lt;/code&gt;. After folding, the compiler replaces uses of that constant. It then propagates constants into other expressions: e.g. &lt;code&gt;let z = y * 2u8;&lt;/code&gt; becomes &lt;code&gt;let z = 16u8&lt;/code&gt;. Folding catches dead code (like code that always fails constraints) as early errors. Multiple passes of folding+propagation may be needed: one pass might reveal new constant expressions, which are then folded again.&lt;/li&gt;
    &lt;li id=&quot;KDjT&quot;&gt;&lt;strong&gt;Loop unrolling:&lt;/strong&gt; Loops in Leo must have a statically known bound. For example, &lt;code&gt;for i: u8 in 0u8..4u8 { ... }&lt;/code&gt; will always iterate exactly 4 times. The compiler replaces such loops with multiple copies of the loop body, one per iteration. This “flattens” control flow into straight-line code. For instance: &lt;code&gt;let sum: u64 = 0u64; for i: u8 in 0u8..4u8 { sum = sum + arr[i]; } &lt;/code&gt; becomes (after unrolling) something like: &lt;code&gt;let sum: u64 = 0u64; sum = sum + arr[0u8]; sum = sum + arr[1u8]; sum = sum + arr[2u8]; sum = sum + arr[3u8]; &lt;/code&gt; Since &lt;code&gt;i&lt;/code&gt; is constant on each iteration, each loop body is duplicated with the appropriate index. (Leo’s docs note that array indices must be constant expressions, so this unrolling is always possible) The result is that after unrolling, &lt;strong&gt;no loops remain&lt;/strong&gt; in the AST.&lt;/li&gt;
    &lt;li id=&quot;RqEt&quot;&gt;&lt;strong&gt;Function inlining:&lt;/strong&gt; Similarly, user-defined functions and circuit definitions are inlined at their call sites. Every call &lt;code&gt;foo(x,y)&lt;/code&gt; is replaced by the body of &lt;code&gt;foo&lt;/code&gt;, substituting parameters with the arguments. After inlining, &lt;strong&gt;no function calls remain&lt;/strong&gt; in the AST. (Leo allows users to write generic functions, but all are expanded at compile time.)&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;gBu5&quot;&gt;Because these optimizations remove high-level constructs (loops and functions), they “flatten” the program into a straight-line sequence of primitive operations. This flattening is crucial: the final circuit generator only handles basic arithmetic and boolean gadgets, not complex control flow. Leo may iterate these optimizations repeatedly until no more constants appear. Notably, the compiler generates a proof after each optimization pass, so we end with a &lt;em&gt;chain of proofs&lt;/em&gt; guaranteeing that the final unfolded AST is semantically equivalent to the original program. The proof also asserts the resulting AST has no loops, no function calls, and all array sizes known.&lt;/p&gt;
  &lt;h2 id=&quot;ZrLN&quot;&gt;Circuit Synthesis (R1CS Code Generation)&lt;/h2&gt;
  &lt;p id=&quot;ComD&quot;&gt;The final phase is &lt;strong&gt;circuit synthesis&lt;/strong&gt;: translating the optimized ASG into an R1CS relation. Leo has a library of &lt;em&gt;R1CS gadgets&lt;/em&gt;, each one encoding a primitive operation as fixed constraints. For example, a gadget for addition outputs one R1CS constraint enforcing &lt;code&gt;z = x + y&lt;/code&gt;. A gadget for boolean checks enforces a variable is 0 or 1 (e.g. &lt;code&gt;b*(b-1)=0&lt;/code&gt;). To compile the ASG, Leo walks the graph of operations and substitutes each primitive with its gadget. This leverages the notion of “handcrafted circuits”: instead of generating thousands of low-level multiply-add gates manually, Leo reuses tested components.&lt;/p&gt;
  &lt;p id=&quot;9g6M&quot;&gt;Practically, this means: every arithmetic expression like &lt;code&gt;a + b&lt;/code&gt; becomes a constraint &lt;code&gt;a + b - t1 = 0&lt;/code&gt; (introducing a new witness &lt;code&gt;t1&lt;/code&gt; for the result), every multiplication &lt;code&gt;a * b&lt;/code&gt; yields a constraint &lt;code&gt;a * b - t2 = 0&lt;/code&gt;, boolean comparisons become binary constraints, etc. A conditional like &lt;code&gt;if cond { X } else { Y }&lt;/code&gt; is handled by introducing a boolean &lt;code&gt;p = (cond&amp;gt;0)&lt;/code&gt;, and then the outputs are computed by a selector: &lt;code&gt;result = p*X + (1-p)*Y&lt;/code&gt; with &lt;code&gt;p*(p-1)=0&lt;/code&gt;. In the R1CS language, each of these relations (flattened into linear equations with one multiplication term per constraint) is added to the circuit.&lt;/p&gt;
  &lt;p id=&quot;yCQW&quot;&gt;The outcome is a &lt;strong&gt;complete R1CS relation&lt;/strong&gt; that is logically equivalent to the original Leo program: for every valid input the constraints have a satisfying assignment of intermediate “wire” values. Leo’s compiler can then output this R1CS (often in JSON form) for a SNARK prover. It even provides tooling to count how many constraints were generated (letting the developer measure cost). For example, the &lt;code&gt;u8&lt;/code&gt; type in Leo is defined as 8 boolean variables under the hood; if you define a new type &lt;code&gt;u4&lt;/code&gt;, Leo uses a gadget with 4 boolean wires and generates the corresponding 4 constraints – ensuring any new user-defined circuit gets compiled into equally efficient constraints.&lt;/p&gt;
  &lt;p id=&quot;uHT6&quot;&gt;Finally, the Aleo platform uses a &lt;strong&gt;universal SNARK&lt;/strong&gt; (Nova/Marlin variant) for proving. Leo’s circuit synthesis supports this by structuring the circuit with a universal structured reference string (SRS): instead of needing a fresh trusted setup per circuit, Leo compiles each program against a universal SRS that any party can update afterwards. In summary, the compiler produces an R1CS circuit and proof-of-correctness; the Leo developer then uses Aleo’s proving system to generate or verify proofs off-chain or in smart contracts.&lt;/p&gt;
  &lt;h2 id=&quot;sb2F&quot;&gt;Example - High-Level Leo to R1CS&lt;/h2&gt;
  &lt;p id=&quot;E1tG&quot;&gt;To see this process in action, consider a concrete Leo snippet. Suppose we have a transition (program) that computes a conditional sum of an array:&lt;/p&gt;
  &lt;pre id=&quot;LqI2&quot;&gt;program sum_example.aleo {
    // Compute either a constant or the sum of an array
    transition compute(a: u64, b: u64, arr: [u64;4]) -&amp;gt; u64 {
        let mut total: u64 = 0u64;
        if a &amp;gt; b {
            // unrolled loop will iterate from 0..4u8
            for i: u8 in 0u8..4u8 {
                total = total + arr[i];
            }
        } else {
            total = b;
        }
        return total;
    }
}&lt;/pre&gt;
  &lt;p id=&quot;bQK4&quot;&gt;&lt;strong&gt;Parsing/AST.&lt;/strong&gt; The parser reads the above text into a Grammar AST; the AST conversion then produces a tree roughly like:&lt;/p&gt;
  &lt;pre id=&quot;d8L3&quot;&gt;TransitionDecl(name=&amp;quot;compute&amp;quot;, params=[(&amp;quot;a&amp;quot;,u64),(&amp;quot;b&amp;quot;,u64),(&amp;quot;arr&amp;quot;,[u64;4])], returnType=u64,
  Block([
    LetStmt(var=&amp;quot;total&amp;quot;, type=u64, value=0u64, mutable=true),
    IfStmt(cond=BinOp(&amp;quot;&amp;gt;&amp;quot;, Var(&amp;quot;a&amp;quot;),Var(&amp;quot;b&amp;quot;)),
      then=Block([
        ForLoop(init=(&amp;quot;i&amp;quot;,u8,0u8), end=4u8, 
          body=Block([
            Assignment(var=&amp;quot;total&amp;quot;, value=BinOp(&amp;quot;+&amp;quot;, Var(&amp;quot;total&amp;quot;), Access(arr, Var(&amp;quot;i&amp;quot;))))
          ])
        )
      ]),
      else=Block([
        Assignment(var=&amp;quot;total&amp;quot;, value=Var(&amp;quot;b&amp;quot;))
      ])
    ),
    ReturnStmt(expr=Var(&amp;quot;total&amp;quot;))
  ])
)&lt;/pre&gt;
  &lt;p id=&quot;3YCe&quot;&gt;This AST captures the high-level structure: a let-binding, an &lt;code&gt;if&lt;/code&gt;, a &lt;code&gt;for&lt;/code&gt;, and return. At this stage, no changes have been made except cleaning up syntax.&lt;/p&gt;
  &lt;p id=&quot;LCBV&quot;&gt;&lt;strong&gt;ASG.&lt;/strong&gt; Converting to the ASG adds cross-links: each use of &lt;code&gt;a&lt;/code&gt;, &lt;code&gt;b&lt;/code&gt;, &lt;code&gt;arr&lt;/code&gt;, &lt;code&gt;total&lt;/code&gt;, and &lt;code&gt;i&lt;/code&gt; is linked to its declaration. The &lt;code&gt;&amp;gt;&lt;/code&gt;, &lt;code&gt;+&lt;/code&gt;, and array access are tagged with expected types (u64 for the sum, u8 index, etc.). For example, the &lt;code&gt;ForLoop&lt;/code&gt; node knows that &lt;code&gt;i: u8&lt;/code&gt; ranges from 0 to 4. If we imagine part of this ASG, the &lt;code&gt;Var(&amp;quot;total&amp;quot;)&lt;/code&gt; nodes in the loop body point back to the &lt;code&gt;LetStmt(&amp;quot;total&amp;quot;)&lt;/code&gt;. No semantic errors occur since types are consistent (e.g. &lt;code&gt;arr[i]&lt;/code&gt; is valid: &lt;code&gt;i&lt;/code&gt; is u8, &lt;code&gt;arr&lt;/code&gt; is &lt;code&gt;[u64;4]&lt;/code&gt;).&lt;/p&gt;
  &lt;p id=&quot;hIcU&quot;&gt;&lt;strong&gt;Canonicalization.&lt;/strong&gt; After canonicalization, the AST/ASG looks the same (types were already explicit). We now know the loop bound is the constant 4, and types match syntactic expectations.&lt;/p&gt;
  &lt;p id=&quot;5vwa&quot;&gt;&lt;strong&gt;Type Inference.&lt;/strong&gt; In this snippet all types were given (&lt;code&gt;u64&lt;/code&gt;, &lt;code&gt;u8&lt;/code&gt;, etc.), so type inference simply confirms consistency. One check: &lt;code&gt;0u64&lt;/code&gt; is a u64 literal, so &lt;code&gt;total: u64&lt;/code&gt; is correct. The compiler verifies no types are missing or mismatched.&lt;/p&gt;
  &lt;p id=&quot;sOa9&quot;&gt;&lt;strong&gt;Optimizations.&lt;/strong&gt; Now we optimize. The loop &lt;code&gt;for i in 0..4&lt;/code&gt; has a constant bound 4, so we unroll it. The compiler replaces the loop with four copies of its body (for &lt;code&gt;i=0,1,2,3&lt;/code&gt;). The code becomes:&lt;/p&gt;
  &lt;pre id=&quot;Gym2&quot;&gt;let total: u64 = 0u64;
if a &amp;gt; b {
    total = total + arr[0u8];
    total = total + arr[1u8];
    total = total + arr[2u8];
    total = total + arr[3u8];
} else {
    total = b;
}
return total;&lt;/pre&gt;
  &lt;p id=&quot;CY4a&quot;&gt;Here we see “no loops” and each index &lt;code&gt;0u8..3u8&lt;/code&gt; is inlined. There are also no function calls to inline. Constant folding does little (total starts at 0, nothing else constant). All &lt;code&gt;u8&lt;/code&gt; and &lt;code&gt;u64&lt;/code&gt; types remain the same.&lt;/p&gt;
  &lt;p id=&quot;bs9k&quot;&gt;&lt;strong&gt;ASG after optimization.&lt;/strong&gt; The ASG now has four consecutive &lt;code&gt;Assignment(&amp;quot;total&amp;quot;, total + arr[i])&lt;/code&gt; nodes (with &lt;code&gt;i&lt;/code&gt; replaced by concrete constants). The &lt;code&gt;IfStmt&lt;/code&gt; still exists at this level.&lt;/p&gt;
  &lt;p id=&quot;0Nuo&quot;&gt;&lt;strong&gt;Circuit Synthesis.&lt;/strong&gt; Finally, Leo emits constraints. It introduces a boolean witness &lt;code&gt;p = (a &amp;gt; b)&lt;/code&gt; using a comparison gadget. Concretely, it will compile &lt;code&gt;a &amp;gt; b&lt;/code&gt; to something like: compute &lt;code&gt;t = a - b&lt;/code&gt;, assert &lt;code&gt;p * (p - 1) = 0&lt;/code&gt; (forcing &lt;code&gt;p&lt;/code&gt; to be 0 or 1), and assert &lt;code&gt;p = 1&lt;/code&gt; if &lt;code&gt;t&lt;/code&gt; is nonzero (via another gadget). Then for the assignments inside the if: on the “then” branch, &lt;code&gt;total&lt;/code&gt; accumulates &lt;code&gt;arr[0] + arr[1] + arr[2] + arr[3]&lt;/code&gt;; on the “else” branch, &lt;code&gt;total = b&lt;/code&gt;. The compiler will enforce a final constraint&lt;/p&gt;
  &lt;pre id=&quot;6sH8&quot;&gt;total = p*(arr[0]+arr[1]+arr[2]+arr[3]) + (1-p)*b.
&lt;/pre&gt;
  &lt;p id=&quot;h3UF&quot;&gt;In R1CS form this becomes linear equations. For example, writing a few constraints:&lt;/p&gt;
  &lt;pre id=&quot;EJpe&quot;&gt;t1 = a - b
p*(p-1) = 0                    // p is boolean 0/1
total_then = arr[0] + arr[1] + arr[2] + arr[3]
total = p * total_then + (1-p) * b&lt;/pre&gt;
  &lt;p id=&quot;GTVf&quot;&gt;(Each equation would be turned into one or more rank-1 constraints by introducing intermediary variables.) What matters is that the &lt;em&gt;same logic&lt;/em&gt; has been captured by algebraic constraints. The boolean &lt;code&gt;p&lt;/code&gt; effectively selects the path, and the sum of the array elements is computed by four additions (one per unrolled step). In practice, Leo’s gadget library might do this slightly differently (e.g. adding one by one), but the net result is the same. The final R1CS encodes both branches of the &lt;code&gt;if&lt;/code&gt; and ensures &lt;code&gt;total&lt;/code&gt; is correct.&lt;/p&gt;
  &lt;p id=&quot;eweG&quot;&gt;In summary, the original high-level code with a loop and branch is lowered to straight-line arithmetic relations. Each high-level construct becomes a small network of constraints: &lt;strong&gt;comparison gadgets&lt;/strong&gt; for &lt;code&gt;&amp;gt;&lt;/code&gt;, &lt;strong&gt;addition gadgets&lt;/strong&gt; for &lt;code&gt;+&lt;/code&gt;, &lt;strong&gt;boolean gadgets&lt;/strong&gt; for &lt;code&gt;if&lt;/code&gt;, etc. This example illustrates the general pattern: complex control flow is flattened, and only basic arithmetic/logic remains at circuit time.&lt;/p&gt;
  &lt;h2 id=&quot;aeF8&quot;&gt;Leo vs Circom vs ZoKrates&lt;/h2&gt;
  &lt;p id=&quot;7zKP&quot;&gt;Leo’s compilation model shares goals with other ZK DSLs like Circom and ZoKrates, but there are important differences in design and developer experience. Below we compare along several dimensions:&lt;/p&gt;
  &lt;ul id=&quot;0Q1T&quot;&gt;
    &lt;li id=&quot;mLKu&quot;&gt;&lt;strong&gt;Language Paradigm:&lt;/strong&gt; Leo is a &lt;strong&gt;general-purpose, Rust-like&lt;/strong&gt; language with rich features: functions, generics, structs, records, tuples, static arrays, etc. Circom is a &lt;strong&gt;domain-specific “circuit” language&lt;/strong&gt;, where programs are built from &lt;strong&gt;templates (components)&lt;/strong&gt; that define constraints. Circom has no traditional functions, only templates and a fixed main circuit. ZoKrates is a &lt;strong&gt;C-like imperative DSL&lt;/strong&gt;: it has functions, mutable variables (with &lt;code&gt;mut&lt;/code&gt;), &lt;code&gt;for&lt;/code&gt; loops, structs, and static arrays, but no objects or generics beyond templates.&lt;/li&gt;
    &lt;li id=&quot;6dhP&quot;&gt;&lt;strong&gt;Control Flow and Loops:&lt;/strong&gt; Leo supports arbitrary nested loops and conditionals in code, but they must have static bounds (so all loops are unrolled). Circom 2 introduced some control constructs (like looping templates), but generally designers use recursion at the template level or replicate components manually. ZoKrates allows for-loops with constant bounds (e.g. &lt;code&gt;for u32 i in 0..5 { ... }&lt;/code&gt;) and &lt;code&gt;if&lt;/code&gt; statements. All three perform &lt;strong&gt;loop unrolling&lt;/strong&gt;: Leo explicitly at compile-time, ZoKrates as part of compilation, and Circom requires unrolling via recursive template instantiation or macros.&lt;/li&gt;
    &lt;li id=&quot;rvHE&quot;&gt;&lt;strong&gt;Type System:&lt;/strong&gt; Leo has a strong static type system with inference: types must be explicit by R1CS time, and the compiler infers missing ones (emitting errors if ambiguous). It includes rich algebraic types: field elements, integers (&lt;code&gt;u8&lt;/code&gt;, &lt;code&gt;u32&lt;/code&gt;, etc.), booleans, addresses, and user-defined structs/records. ZoKrates also has a static type system with two primitives (&lt;code&gt;field&lt;/code&gt; and &lt;code&gt;bool&lt;/code&gt;) and supports &lt;code&gt;u32&lt;/code&gt;, &lt;code&gt;u64&lt;/code&gt; etc. with overflow semantics, plus static arrays/structs. Circom’s type system is more limited: it has “signals” (wires) and public/private keywords, but all data is essentially elements in a field; it does allow tuples and array types, but no user-defined structs (as of Circom 2). Importantly, &lt;strong&gt;Leo’s AST and ASG passes are formally verified&lt;/strong&gt; to respect type rules, whereas Circom and ZoKrates compilers are not formally checked.&lt;/li&gt;
    &lt;li id=&quot;j6nS&quot;&gt;&lt;strong&gt;Optimizations:&lt;/strong&gt; All three compilers perform similar optimizations (constant folding, dead code elimination, etc.), but the tooling differs. Circom’s compiler has built-in &lt;strong&gt;constraint simplification&lt;/strong&gt;: a &lt;code&gt;--O2&lt;/code&gt; mode does constant propagation on the constraint equations. For example, unused wires can be eliminated at Circom compile time. ZoKrates similarly folds constants and unrolls loops during its IR-to-circuit lowering, though it exposes fewer optimization knobs. Leo explicitly documents its optimization passes: constant folding, propagation, unrolling, inlining run until fixpoint. Because Leo emits a proof for each optimization step, it can guarantee those transforms are semantics-preserving. In contrast, Circom and ZoKrates do not produce such proofs (though Circom’s constraint simplifier is known to occasionally remove constraints if undefined, see discussions).&lt;/li&gt;
    &lt;li id=&quot;7Bhu&quot;&gt;&lt;strong&gt;Proof System Integration:&lt;/strong&gt; Leo is built for the Aleo stack, which uses a &lt;em&gt;universal&lt;/em&gt; SNARK (Nova/Marlin) with an updatable SRS. The Leo compiler supports generating circuits that work with Aleo’s proving setup: developers can use a single universal trusted setup across all programs. Circom is commonly paired with Groth16 proofs (e.g. via snarkjs and EVM verifiers), though newer tools also support PLONK. ZoKrates supports multiple schemes (Groth16, Plonk, Marlin) and can output Solidity verifiers for Ethereum. In terms of proofs, Leo’s difference is formal: its entire pipeline is designed around formal verification of compiler steps, whereas Circom/ZoKrates compilers are not.&lt;/li&gt;
    &lt;li id=&quot;wqrN&quot;&gt;&lt;strong&gt;Developer Experience:&lt;/strong&gt; Leo provides a modern developer toolkit: a Cargo-like package manager for crates, a local/remote compiler service, an official playground, and built-in testing utilities. Circom has a simpler setup (each circuit is a &lt;code&gt;.circom&lt;/code&gt; file, compiled with command-line tools); it has libraries of template components (circomlib) but no standard package system. ZoKrates offers an interactive CLI and browser IDE, and defines a JSON ABI, but also lacks a formal typecheck framework. Leo’s language syntax (inspired by Rust) and formal tooling can be more approachable for experienced developers, while Circom’s “hardware description” style is more specialized.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;BBwt&quot;&gt;The table below summarizes some key contrasts:&lt;/p&gt;
  &lt;figure id=&quot;BPTZ&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/42/06/42062a90-ae24-4211-a033-c51d727e939b.png&quot; width=&quot;653&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;ob4u&quot;&gt;Overall, Leo aims for &lt;em&gt;expressiveness and correctness&lt;/em&gt;: developers can write familiar high-level code, rely on formal checks, and still target optimized SNARK circuits. Circom and ZoKrates sacrifice some high-level convenience for simplicity: they expose a more direct view of constraints but lack Leo’s proven guarantees.&lt;/p&gt;
  &lt;h2 id=&quot;GCbX&quot;&gt;Practical Insights for ZK Developers&lt;/h2&gt;
  &lt;p id=&quot;IO7e&quot;&gt;For developers, using Leo offers both high-level abstraction and insight into how code maps to proofs. Because Leo is a compiled language with Rust-like syntax, experienced programmers can leverage loops, functions, and types instead of manually wiring gates. At the same time, &lt;strong&gt;Leo’s compile-time feedback is rigorous&lt;/strong&gt;: type errors or unsupported patterns are caught early, and developers see how constants and control flow are handled. For example, knowing loops will be unrolled encourages writing loops only when bounds are small. The built-in testing framework lets developers write unit tests for transitions without crafting proofs manually.&lt;/p&gt;
  &lt;p id=&quot;WyUv&quot;&gt;Working with Leo deepens one’s understanding of zero-knowledge circuits. Seeing a high-level conditional turn into a boolean selector gadget, or observing how an array sum yields multiple addition constraints, helps developers internalize the cost model of ZK proofs. The Leo REPL and compiler even let you count constraints for a given code snippet, so you can compare different implementations and optimize. In short, Leo hides the low-level constraint boilerplate, but doesn’t obscure it: you can always audit the generated R1CS or witness code if needed. This balance of abstraction and transparency makes Leo a strong pedagogical tool.&lt;/p&gt;
  &lt;p id=&quot;yHrj&quot;&gt;Moreover, Leo is tightly integrated with the Aleo ecosystem: compiled programs can be deployed to Aleo’s private network, and proofs easily verified on-chain with provided Solidity verifiers. The universal SNARK setup means developers don’t fuss over trusted setup for each circuit. Learning Leo thus directly builds skills applicable to secure app development on Aleo (and by extension, understanding generic ZK-SNARK systems).&lt;/p&gt;
  &lt;p id=&quot;SzmH&quot;&gt;Inspiringly, Leo’s formal approach raises confidence: every Leo program comes with a &lt;em&gt;machine-checked guarantee&lt;/em&gt; that its circuit matches the code. In an industry where trust in compiler correctness is crucial, this is a distinctive advantage. For ZK practitioners, Leo represents a mature, well-engineered platform where one can focus on building logic, while the compiler handles the heavy lifting of circuit construction and verification.&lt;/p&gt;
  &lt;p id=&quot;Cw5h&quot;&gt;&lt;strong&gt;In summary&lt;/strong&gt;, the Leo compiler provides a clear, rigorous pathway from high-level code to arithmetic circuits. Its multi-stage pipeline (parsing → AST → ASG → optimized ASG → R1CS) ensures both efficiency and correctness. By comparison, Circom and ZoKrates offer lighter-weight, DSL-centric toolchains. Developers choosing Leo benefit from its expressiveness, optimization, and formal guarantees, making it an attractive entry into the world of zero-knowledge programming and the growing Aleo ecosystem.&lt;/p&gt;
  &lt;p id=&quot;ak4E&quot;&gt;&lt;strong&gt;Write by alexanderblv for the Aleo, September 2025 &lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;KI1X&quot;&gt;&lt;strong&gt;&lt;a href=&quot;https://x.com/alexander_blv&quot; target=&quot;_blank&quot;&gt;x.com/alexander_blv&lt;/a&gt; &lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;ZD3E&quot;&gt;&lt;strong&gt;ERC20 - 0x1e1Aa06ff5DC84482be94a216483f946D0bC67e7&lt;/strong&gt;&lt;/p&gt;

</content></entry><entry><id>alexanderblv:aleo-bft</id><link rel="alternate" type="text/html" href="https://teletype.in/@alexanderblv/aleo-bft?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=alexanderblv"></link><title>AleoBFT. Formal Specification and Security Analysis of a Hybrid Consensus</title><published>2025-07-30T13:36:28.026Z</published><updated>2025-07-30T13:36:28.026Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img3.teletype.in/files/64/ac/64ace99a-8aaf-4c99-9df0-69706ebd21af.png"></media:thumbnail><summary type="html">&lt;img src=&quot;https://img3.teletype.in/files/21/03/21037d68-5dd8-4fdd-b9cc-9a1bf8258d5a.png&quot;&gt;AleoBFT - hybrid consensus protocol combining DAG-based Narwhal and Bullshark with dynamic Proof-of-Stake committees and a novel Proof-of-Succinct-Work (PoSW) incentive. This article explores its formal design, security guarantees, instant finality, and how it outperforms protocols like Tendermint, HotStuff, and Ouroboros while enabling decentralized zk-SNARK proving at scale</summary><content type="html">
  &lt;p id=&quot;hsO5&quot;&gt;AleoBFT is the hybrid consensus protocol at the core of the Aleo privacy-preserving blockchain. It builds on the DAG-based Narwhal and Bullshark algorithms, extended with dynamic Proof-of-Stake committees and a novel Proof-of-Succinct-Work (PoSW) &lt;em&gt;coinbase puzzle&lt;/em&gt; incentive. AleoBFT yields &lt;strong&gt;instant finality&lt;/strong&gt; and high throughput while supporting decentralized proving of zk-SNARKs. We now describe AleoBFT’s design and security in detail, with formal definitions, key lemmas, and comparisons to other protocols.&lt;/p&gt;
  &lt;h2 id=&quot;odOA&quot;&gt;Formal Protocol Specification&lt;/h2&gt;
  &lt;p id=&quot;xmTA&quot;&gt;Formally, AleoBFT is modeled as a labeled state transition system . Each &lt;em&gt;validator&lt;/em&gt; has an address (public key) and communicates over a partially-synchronous network. Validator states include a local &lt;em&gt;Directed Acyclic Graph (DAG)&lt;/em&gt; of certificates and a local &lt;em&gt;blockchain&lt;/em&gt;. Validators proceed in &lt;strong&gt;rounds&lt;/strong&gt; (numbered 1,2,3,…). In each round, each validator may author at most one &lt;em&gt;proposal&lt;/em&gt; (a set of transactions) and ultimately a &lt;em&gt;certificate&lt;/em&gt; (proposal + signatures). Consensus proceeds in two layers:&lt;/p&gt;
  &lt;ul id=&quot;3QLx&quot;&gt;
    &lt;li id=&quot;2cLY&quot;&gt;&lt;strong&gt;Narwhal (Mempool Layer)&lt;/strong&gt; Validators collect transactions (from clients and provers), form proposals, broadcast them, endorse others’ proposals by signing, and assemble endorsements into &lt;em&gt;certificates&lt;/em&gt;. Certificates encode proposals and meet strict consistency rules.&lt;/li&gt;
    &lt;li id=&quot;JiQS&quot;&gt;&lt;strong&gt;Bullshark (Ordering Layer)&lt;/strong&gt; Using the DAG of certificates, validators execute a commit rule to linearize and finalize blocks. Certain round-based &lt;em&gt;anchor certificates&lt;/em&gt; determine blockchain updates.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;QSxd&quot;&gt;The Aldgo spec defines events like “CreateProposal”, “ReceiveProposal”, “StoreCertificate”, “AdvanceRound”, and “CommitAnchor”. A &lt;strong&gt;certificate&lt;/strong&gt; is a tuple &lt;strong&gt;Cert&lt;/strong&gt;=(author, round, transactions, references, endorsers) where each certificate has one author and one round. Each validator signs and multicasts proposals; once it collects ≥&lt;em&gt;(n–f)&lt;/em&gt; endorsements (where &lt;em&gt;n&lt;/em&gt; is total validators, &lt;em&gt;f&lt;/em&gt; is max Byzantine), it creates a certificate and broadcasts it. All correct nodes validate received certificates and insert them in their DAG. The &lt;strong&gt;non-equivocation property&lt;/strong&gt; holds: if a certificate for (author, round) exists, it is unique across all honest views.&lt;/p&gt;
  &lt;p id=&quot;8jVs&quot;&gt;The official AleoBFT specification (Acl2-verified) proves two central invariants: &lt;strong&gt;certificate non-equivocation&lt;/strong&gt; and &lt;strong&gt;blockchain non-forking&lt;/strong&gt;. The Narwhal component guarantees non-equivocation (one certificate per author/round), while the Bullshark ordering ensures that all honest nodes commit the same sequence of certificates (no divergent chains). Formally, if at most &lt;em&gt;f&lt;/em&gt; validators are Byzantine (with &lt;em&gt;n ≥ 3f+1&lt;/em&gt;), then &lt;strong&gt;all validators commit the same anchors in the same order&lt;/strong&gt;. This yields strong &lt;strong&gt;safety&lt;/strong&gt;: once an anchor certificate is committed, its transactions become part of the final chain on all honest nodes. Liveness is assured under the usual partial-synchrony model: after some unknown Global Stabilization Time (GST), a correct leader will propagate proposals and commit certificates at network speed.&lt;/p&gt;
  &lt;pre id=&quot;ZmgQ&quot;&gt;# (Pseudo) validation loop for AleoBFT (Narwhal + Bullshark)
while True:
    event = network.receive()
    if event.type == &amp;quot;Proposal&amp;quot;:      # a new proposal message arrived
        if verify_signature(event.proposal, event.author):
            # Endorse and send signature back to author
            signature = sign(event.proposal)
            network.send(event.author, {&amp;quot;type&amp;quot;: &amp;quot;Signature&amp;quot;,
                                        &amp;quot;id&amp;quot;: event.proposal.id,
                                        &amp;quot;signature&amp;quot;: signature})
    elif event.type == &amp;quot;Signature&amp;quot;:   # endorsement for our own proposal
        proposal_id = event.id
        cert_state[proposal_id].add_signature(event.signature)
        if cert_state[proposal_id].has_quorum():
            # Got ≥ n-f signatures; form and broadcast certificate
            certificate = make_certificate(proposal_id)
            network.broadcast({&amp;quot;type&amp;quot;: &amp;quot;Certificate&amp;quot;, &amp;quot;certificate&amp;quot;: certificate})
    elif event.type == &amp;quot;Certificate&amp;quot;:
        cert = event.certificate
        if verify_certificate(cert):
            store_in_dag(cert)
            # If this cert is an anchor (even round) and has ≥ f+1 votes, commit it
            if cert.round % 2 == 0 and count_votes(cert) &amp;gt;= (f+1):
                commit_anchor(cert)  # append to blockchain
&lt;/pre&gt;
  &lt;p id=&quot;jtkX&quot;&gt;In this pseudocode, “count_votes(cert)” tallies how many later-round certificates reference this anchor. The threshold &lt;em&gt;f+1&lt;/em&gt; (for committing an anchor) stems from Bullshark’s DAG commit rule. Importantly, the formal spec proves that under these rules &lt;strong&gt;all honest validators will commit an identical ledger&lt;/strong&gt; (no forks).&lt;/p&gt;
  &lt;h2 id=&quot;IhC0&quot;&gt;Narwhal. Data Dissemination and the Certificate DAG&lt;/h2&gt;
  &lt;p id=&quot;8uEa&quot;&gt;AleoBFT separates &lt;em&gt;data dissemination&lt;/em&gt; from &lt;em&gt;ordering&lt;/em&gt;. In the &lt;strong&gt;Narwhal&lt;/strong&gt; layer, validators exchange proposals and signatures, building a directed acyclic graph of certificates. Each round, every validator creates at most one proposal (message) which includes: a unique ID, the author’s signature, a set of transactions, and &lt;em&gt;references&lt;/em&gt; to certificates from the previous round. Collecting signatures yields a certificate which is then broadcast and stored by all.&lt;/p&gt;
  &lt;p id=&quot;K4U0&quot;&gt;This forms a &lt;strong&gt;round-based DAG&lt;/strong&gt;: rows correspond to validators (authors), columns to rounds. Each certificate (node) has edges pointing to referenced certificates in the prior round. By protocol, each message in round &lt;em&gt;r&lt;/em&gt; must reference at least &lt;em&gt;(n–f)&lt;/em&gt; certificates from round &lt;em&gt;r–1&lt;/em&gt;, ensuring broad overlap of data. Figure 1 illustrates a round-based DAG example (4 validators, &lt;em&gt;f&lt;/em&gt;=1). Each validator’s proposal is a white box per round, with blue arrows referencing earlier certificates.&lt;/p&gt;
  &lt;p id=&quot;9D6d&quot;&gt;Even if up to &lt;em&gt;f&lt;/em&gt; validators behave maliciously, each honest certificate in a round will reference ≥&lt;em&gt;n–f&lt;/em&gt; nodes, so that any two views intersect heavily. Narwhal uses a reliable-broadcast abstraction so that honest validators deliver the same certificates. Hence:&lt;/p&gt;
  &lt;ul id=&quot;nj43&quot;&gt;
    &lt;li id=&quot;80RC&quot;&gt;&lt;strong&gt;Non-equivocation. &lt;/strong&gt;Any two honest nodes that have a certificate from validator &lt;em&gt;v&lt;/em&gt; in round &lt;em&gt;r&lt;/em&gt; will have identical content (same transactions and references). Equivalently, a faulty validator cannot cause two conflicting certificates in the DAG; if it signs at most one per round, it cannot equivocate. This is formally enforced by requiring cryptographic signatures on proposals and by validators refusing to accept certificates with invalid sigs or insufficient endorsements.&lt;/li&gt;
    &lt;li id=&quot;lul5&quot;&gt;&lt;strong&gt;Complete data.&lt;/strong&gt; By requiring ≥&lt;em&gt;(n–f)&lt;/em&gt; references, Narwhal guarantees that when validators advance rounds, they have seen sufficient honest information. (A validator can only advance to round &lt;em&gt;r&lt;/em&gt; after storing ≥&lt;em&gt;(n–f)&lt;/em&gt; certificates in round &lt;em&gt;r–1&lt;/em&gt;.) This avoids spurious forks due to missing data.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;afvz&quot;&gt;Narwhal’s design achieves &lt;em&gt;zero overhead ordering&lt;/em&gt;: once the DAG is built, validators can fully order transactions without additional rounds of messaging. The next phase uses the DAG edges to achieve consensus ordering.&lt;/p&gt;
  &lt;h2 id=&quot;nD50&quot;&gt;Bullshark. Ordering and Anchor Commit Rule&lt;/h2&gt;
  &lt;p id=&quot;0MRV&quot;&gt;The &lt;strong&gt;Bullshark&lt;/strong&gt; layer orders the DAG into a single blockchain with finality. Crucially, certain certificates are designated &lt;strong&gt;anchors&lt;/strong&gt; (preassigned leaders) each even round (e.g. Validator2 in round 2, Validator3 in round 4, etc.). An anchor is simply the certificate authored by that round’s leader; once an anchor is &lt;em&gt;committed&lt;/em&gt;, all transactions in its causal history become final.&lt;/p&gt;
  &lt;p id=&quot;NP3H&quot;&gt;Bullshark’s commit rule is elegantly simple: &lt;strong&gt;an anchor certificate commits as soon as it gathers ≥ &lt;em&gt;f+1&lt;/em&gt; votes from the next round&lt;/strong&gt;. Here, a “vote” is represented by an edge: each certificate in round &lt;em&gt;r+1&lt;/em&gt; votes for the previous round’s anchor if it references it (directly or transitively). Since every round-(&lt;em&gt;r+1&lt;/em&gt;) certificate must reference ≥&lt;em&gt;(n–f)&lt;/em&gt; round-&lt;em&gt;r&lt;/em&gt; certificates, each anchor will automatically gather a large number of votes. In fact, an honest anchor in round &lt;em&gt;r&lt;/em&gt; will have at least &lt;em&gt;(n–2f)&lt;/em&gt; votes in round &lt;em&gt;r+1&lt;/em&gt;, so just &lt;em&gt;f+1&lt;/em&gt; suffices to ensure at least one honest endorsement.&lt;/p&gt;
  &lt;p id=&quot;cfGP&quot;&gt;Round1’s anchor (A1, green) and round3’s anchor (A2) are highlighted. In round4, several validators produce certificates that reference A1 or A2. The pink arrows mark votes from round4 certificates. Here A2 accumulates enough votes (pink edges) to reach the &lt;em&gt;f+1&lt;/em&gt; threshold and thus is &lt;strong&gt;committed&lt;/strong&gt; (trophy icon); A1 does not get enough votes and remains uncommitted. By design, once an anchor is committed, every honest validator will place its causal history (all earlier DAG nodes reachable from it) into the chain in a fixed order. This guarantees that:&lt;/p&gt;
  &lt;ul id=&quot;qG7w&quot;&gt;
    &lt;li id=&quot;CTxa&quot;&gt;&lt;strong&gt;No forks. &lt;/strong&gt;All honest validators commit the same anchors in the same order. The Bullshark proof shows that the voting structure and “safe-to-skip” rules force a unique linear sequence of anchors. Intuitively, quorum intersection and DAG paths prevent any two anchors from both being considered committed in conflicting ways.&lt;/li&gt;
    &lt;li id=&quot;yhs0&quot;&gt;&lt;strong&gt;Instant finality.&lt;/strong&gt; When an anchor certificate is committed (on witnessing &lt;em&gt;f+1&lt;/em&gt; votes), the transactions it carries are immediately final. There is no subsequent chance of reversion or conflict. Unlike longest-chain protocols, AleoBFT’s finality is immediate after the commit event.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;d7gf&quot;&gt;Behind the scenes, the AleoBFT spec models Bullshark as a state machine with events like &lt;code&gt;CommitCond(state, val)&lt;/code&gt;, updating the local blockchain when an anchor achieves sufficient incoming references. The formal theorem proved in ACL2 states that &lt;strong&gt;blockchain non-forking&lt;/strong&gt; holds: if &lt;em&gt;f &amp;lt; n/3&lt;/em&gt; and the network eventually delivers messages, then no two validators can commit different anchors or blocks.&lt;/p&gt;
  &lt;h2 id=&quot;EHhj&quot;&gt;Dynamic Committees and Staking&lt;/h2&gt;
  &lt;p id=&quot;rVk5&quot;&gt;Unlike traditional BFT systems with static validators, AleoBFT supports &lt;strong&gt;dynamic committees with Proof-of-Stake&lt;/strong&gt;. At genesis, a known set of validators is defined. Thereafter, validators enter or exit the committee via on-chain staking transactions. Each validator has a stake, and consensus decisions weight validators by stake. The ACL2 spec includes stake by replacing “number of validators” with “total stake” when forming quorums. In practice, this means a certificate must collect signatures from holders of &amp;gt;2/3 total stake, and an anchor needs &amp;gt;1/3 stake of voting weight.&lt;/p&gt;
  &lt;p id=&quot;xMEL&quot;&gt;Committee changes occur on-chain: stakers delegate to candidates, and slashing or unbonding modifies the validator set. AleoBFT is designed so that committees &lt;strong&gt;can change every block&lt;/strong&gt; if needed. The formal proof shows that even with committees that shift each round based on previous blocks, safety still holds. The dynamic-committee model is significantly more complex than static consensus, but it was proven in ACL2 that &lt;em&gt;“blockchains of different validators never fork”&lt;/em&gt; even when the committee can change unpredictably.&lt;/p&gt;
  &lt;p id=&quot;r2gD&quot;&gt;In effect, AleoBFT is a hybrid &lt;strong&gt;BFT PoS&lt;/strong&gt; system: &lt;em&gt;stake&lt;/em&gt; secures the protocol (malicious validators are presumed to be &amp;lt;1/3 of stake), but &lt;em&gt;PoSW&lt;/em&gt; mining incentive drives prover hardware investments. Validators must hold stake to participate, but actual block proposal and voting follows DAG/BFT rules, not Nakamoto’s longest chain. This preserves strict finality while allowing an open, stake-weighted committee.&lt;/p&gt;
  &lt;h2 id=&quot;Q2oi&quot;&gt;Proof of Succinct Work (PoSW)&lt;/h2&gt;
  &lt;p id=&quot;v1P8&quot;&gt;A key innovation in Aleo is &lt;em&gt;Proof-of-Succinct Work&lt;/em&gt; (PoSW) for &lt;em&gt;prover incentivization&lt;/em&gt;. Provers (specialized hardware operators) do &lt;strong&gt;useful ZK-work&lt;/strong&gt; instead of futile hashing. Each block has a “coinbase puzzle” whose solution is a zk-SNARK computation. Provers compete to solve this puzzle to earn a share of the block reward, but &lt;strong&gt;block production and ordering remain controlled by the BFT protocol&lt;/strong&gt; (provers do &lt;em&gt;not&lt;/em&gt; decide the blockchain tips).&lt;/p&gt;
  &lt;p id=&quot;KCDu&quot;&gt;Specifically, every block includes a &lt;em&gt;coinbase puzzle&lt;/em&gt; involving multi-scalar multiplications (MSM) and Fast Fourier Transforms (FFT) – core subcomputations of zk-SNARK proving. Provers run optimized GPUs/ASICs to accelerate these tasks. When a prover finds a solution (a succinct SNARK that verifies some portion of the puzzle), it submits it as an additional certificate type. The block’s creator (anchor author) aggregates proofs from all provers who solved the puzzle and includes them in the block. Importantly:&lt;/p&gt;
  &lt;ul id=&quot;HQl1&quot;&gt;
    &lt;li id=&quot;Np62&quot;&gt;&lt;strong&gt;Useful work.&lt;/strong&gt; The puzzle forces provers to perform real ZK computation (MSM, FFT), speeding up zk-SNARK proof-generation for users. As described by the engineering team: &lt;em&gt;“the Coinbase puzzle directs provers to perform useful computations like multi-scalar multiplication and fast Fourier transforms, which are essential building blocks in the ZKP-generation process”&lt;/em&gt;. This means even work by “losing” provers contributes to overall network efficiency.&lt;/li&gt;
    &lt;li id=&quot;xBtk&quot;&gt;&lt;strong&gt;Progressive difficulty. &lt;/strong&gt;The puzzle’s hardness &lt;em&gt;increases over time&lt;/em&gt;, pushing provers to innovate hardware and algorithms. Similar to proof-of-work, raising difficulty drives investment in accelerators, but here the work is ZK-related.&lt;/li&gt;
    &lt;li id=&quot;rgI9&quot;&gt;&lt;strong&gt;Reward distribution.&lt;/strong&gt; Instead of “winner-takes-all”, Aleo disperses rewards proportionally. All provers who solve a block’s puzzle share the coinbase reward. This mimics a mining pool economically but is built into the protocol. Thus, even modest provers can earn partial rewards, encouraging decentralization of proving.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;oBQd&quot;&gt;From a cryptographic standpoint, the PoSW coinbase puzzle relies on standard SNARK primitives. Aleo currently uses &lt;strong&gt;Groth16 zk-SNARKs&lt;/strong&gt; over BLS12-377 (or BLS12-381) curves, with R1CS circuits describing the puzzle. The puzzle can be viewed as: “Find a witness that satisfies a given R1CS derived from randomizing curve base points (for MSM/FFT).” Verification of each solution is extremely fast (SNARK verification is ~millisecond), so nodes can quickly check many solutions on-chain.&lt;/p&gt;
  &lt;p id=&quot;0GMh&quot;&gt;Mathematically, one might sketch the security of PoSW as follows: assume the SNARK is sound (with negligible error), and assume provers have limited parallelism (forms of a VDF, though Aleo does not explicitly publish a VDF, the circuit enforces sequential operations). Then a prover cannot fake a solution faster than doing the underlying arithmetic. Raising the puzzle’s &lt;em&gt;R1CS complexity&lt;/em&gt; over time effectively increases work required per solution.&lt;/p&gt;
  &lt;p id=&quot;UNr8&quot;&gt;PoSW thus ties consensus to cryptographic proofs. Unlike PoW which relies on hash puzzles, PoSW relies on the hardness of exponentiations/FFTs plus zk verification. One can view a block’s coinbase puzzle as:&lt;/p&gt;
  &lt;figure id=&quot;kXEa&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/a7/30/a7309f01-3dd7-4437-b592-286e9e053a2a.png&quot; width=&quot;440&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;zzuH&quot;&gt;where solving means constructing a SNARK verifying these. Each puzzle’s output is a proof σ that &lt;em&gt;“I did this multi-scalar work”&lt;/em&gt;. Because the proof itself is succinct (ZK-SNARK), nodes can verify σ quickly, unlike traditional PoW proofs which still need to check many hash operations. This succinctness is advantageous for scalability.&lt;/p&gt;
  &lt;p id=&quot;b76U&quot;&gt;In summary, PoSW integrates smoothly with the BFT layer: it provides miner incentives and speeds up proving, while the Narwhal/Bullshark protocol governs block validity. Provers do not influence block ordering or consensus safety; they only affect the timing and distribution of rewards. (Indeed, AleoBFT is designed so that &lt;em&gt;any node&lt;/em&gt; can run as a prover and accumulate rewards without being a validator, allowing great decentralization of the prover role.)&lt;/p&gt;
  &lt;h2 id=&quot;YgaA&quot;&gt;Comparison with Other Consensus Protocols&lt;/h2&gt;
  &lt;p id=&quot;T96f&quot;&gt;AleoBFT can be viewed as a hybrid of several ideas: it is a &lt;strong&gt;BFT finality&lt;/strong&gt; layer (like Tendermint/HotStuff) combined with &lt;strong&gt;productive work&lt;/strong&gt; (like PoW) and &lt;strong&gt;PoS staking&lt;/strong&gt;. We compare it against three well-known protocols:&lt;/p&gt;
  &lt;p id=&quot;q2n9&quot;&gt;&lt;strong&gt;Tendermint (PBFT-based BFT)&lt;/strong&gt; Tendermint is a leader-driven BFT engine (in Cosmos SDK) that tolerates f&amp;lt;n/3 and achieves instant finality once a block is committed. Like AleoBFT, Tendermint ensures no forks under &amp;lt;1/3 faults. However, Tendermint uses a strict 3-phase commit (propose, pre-vote, pre-commit) per block, introducing latency bound by the protocol’s worst-case delay &lt;em&gt;Δ&lt;/em&gt;. In contrast, AleoBFT’s DAG approach allows all nodes to collect proposals concurrently and order them without extra rounds. Narwhal/Bullshark is &lt;em&gt;responsive&lt;/em&gt;: after GST, consensus speed is dictated by actual network delay, whereas Tendermint’s latency is tied to preconfigured timeouts. On throughput, Tendermint networks typically achieve on the order of hundreds to low-thousands of TPS (limited by sequential block building and commit signatures), whereas DAG-based systems can reach an &lt;em&gt;order of magnitude higher&lt;/em&gt; throughput by parallelizing proposal dissemination. Decentralization-wise, Tendermint’s validator set is fixed or slowly changing via staking, and it relies on a round-robin leader. AleoBFT’s committees rotate faster and are determined on-chain every block, potentially allowing broader validator churn. Both systems share BFT robustness, but AleoBFT adds PoSW incentives and faster finality for zk-applications.&lt;/p&gt;
  &lt;p id=&quot;NnMi&quot;&gt;&lt;strong&gt;HotStuff &lt;/strong&gt;HotStuff is a modern leader-based BFT (used in Diem/Meta) designed for pipelining and linear view-change. It also requires f&amp;lt;n/3 and gives instant finality after a leader commits. HotStuff’s performance is better than PBFT (it uses a two-phase vote/commit chain), but still all proposals go through a single leader pipeline. In terms of resilience, HotStuff and AleoBFT are similar: once 2f+1 votes are collected for a leader’s proposal, the block is final. However, AleoBFT can commit an anchor with only f+1 votes due to its DAG design, effectively lowering the threshold for commit in practice while still preserving safety. A key difference is that HotStuff operates on a linear chain, whereas AleoBFT’s DAG lets many certificates flow per round. The formal analysis of HotStuff shows linear communication complexity and responsiveness, comparable to Narwhal/Bullshark’s guarantees. But AleoBFT’s data-flow design leads to simpler consensus logic (zero additional messaging for ordering) and higher throughput. Both use BLS signatures or variants, but AleoBFT further embeds zk-SNARK verification into its reward mechanism.&lt;/p&gt;
  &lt;p id=&quot;wFQT&quot;&gt;&lt;strong&gt;Ouroboros (Proof-of-Stake chain)&lt;/strong&gt; Ouroboros (Cardano’s consensus family) is a longest-chain PoS protocol. It tolerates &amp;lt;50% adversarial stake (assuming &amp;gt;51% honest) and has &lt;em&gt;probabilistic finality&lt;/em&gt;: a block becomes “final” after several confirmations. Unlike AleoBFT’s immediate finality, Ouroboros requires waiting ~k blocks to be sure a transaction is permanent. Ouroboros uses cryptographic randomness (VRFs) to elect slot leaders and encourages stake decentralization via pools. In contrast, AleoBFT’s block authorship is not random leader per slot but set leaders per round (deterministic sequence may be pseudorandom based on previous block data). Performance-wise, Ouroboros (like other PoS chains) can achieve modest throughput (hundreds TPS) since it follows a sequential chain. Its safety relies on a &amp;gt;50% honest majority and suffers the usual chain-splitting attacks if adversaries are powerful. AleoBFT, by contrast, requires only &amp;lt;33% malicious stake and cannot be led into long forks because its finality is strict. In terms of incentives, Ouroboros rewards block producers per slot, whereas AleoBFT rewards both validators (for BFT participation) and PoSW provers per block (for solving puzzles). Finally, decentralization: Ouroboros can allow very large committees (any node can stake and be elected leader), whereas AleoBFT’s validator set is explicit but can change every block.&lt;/p&gt;
  &lt;h2 id=&quot;JfLq&quot;&gt;Mathematical Properties and Proof Sketches&lt;/h2&gt;
  &lt;p id=&quot;r50D&quot;&gt;AleoBFT’s formal spec provides theorems and proof outlines for its key properties. The main safety theorem can be stated:&lt;/p&gt;
  &lt;p id=&quot;8UPL&quot;&gt;&lt;strong&gt;Theorem (Non-forking / Consistency) -&lt;/strong&gt; &lt;em&gt;Assume a partially synchronous network and at most f&amp;lt;n/3 validators are Byzantine. Then, in AleoBFT, all honest validators will commit the same sequence of anchor certificates (hence the same blocks).&lt;/em&gt;&lt;/p&gt;
  &lt;p id=&quot;OT9j&quot;&gt;Narwhal ensures non-equivocation, so there is a single DAG of certificates with consistent content. Bullshark’s voting rules guarantee that once any honest node commits an anchor, no other honest node can commit a conflicting anchor. Quorum intersection (any two sets of ≥&lt;em&gt;(n–f)&lt;/em&gt; certificates overlap in ≥&lt;em&gt;(n–2f)&lt;/em&gt;≥&lt;em&gt;(f+1)&lt;/em&gt; nodes) and the “safe-to-skip” logic enforce a common ordering. The formal proof uses induction on round numbers and cases for certificates being witnessed by different subsets of validators, ultimately showing all commit events are consistent.&lt;/p&gt;
  &lt;p id=&quot;ZvC0&quot;&gt;&lt;strong&gt;Lemma (Non-equivocation) -&lt;/strong&gt; &lt;em&gt;For each validator v and round r, at most one certificate by v in round r can be committed. Equivalently, if certificate C (by v in r) exists in the DAG, no honest node can later accept a different certificate by v for that round.&lt;/em&gt;&lt;/p&gt;
  &lt;p id=&quot;nl4z&quot;&gt;This follows from Narwhal’s certificate formation: a validator only creates a certificate after collecting a unique set of signatures for its single proposal. A second certificate would require forging signatures (impossible under honest keys) or equivocating (which the protocol disallows). The ACL2 model explicitly proves that no two distinct certificates by the same author/round can both be stored.&lt;/p&gt;
  &lt;p id=&quot;Gfz9&quot;&gt;&lt;strong&gt;Liveness Condition -&lt;/strong&gt; &lt;em&gt;If the network is synchronous for sufficiently long, and if ≥&lt;/em&gt;(2f+1)* validators follow the protocol, then new proposals and certificates will continue to be created, and anchors will keep committing.*&lt;/p&gt;
  &lt;p id=&quot;zWfi&quot;&gt;After GST, messages arrive in bounded time. In each round, an honest leader will propose (it has no conflict, sees ≥&lt;em&gt;(n–f)&lt;/em&gt; from prior round), others sign, forming a certificate. Because &amp;lt;f are faulty, the certificate will get ≥&lt;em&gt;(n–f)&lt;/em&gt;≥&lt;em&gt;(2f+1)&lt;/em&gt; endorsements, so it is known to all by the end of the round. In the next round, ≥&lt;em&gt;(n–f)&lt;/em&gt; certificates reference that anchor, so it meets the &lt;em&gt;f+1&lt;/em&gt; vote threshold and commits. Thus each new anchor advances the chain every two rounds. No adversarial strategy can block this indefinitely unless &amp;gt;1/3 nodes fail to cooperate.&lt;/p&gt;
  &lt;p id=&quot;BSl6&quot;&gt;Additionally, the formal spec includes proofs that the protocol variables (queues, DAG buffers, timers) evolve correctly. For instance, it shows no deadlock can occur provided at least one correct validator eventually sends messages. (Some timing abstraction is used: validators have “timeout” events to advance rounds if needed.)&lt;/p&gt;
  &lt;h2 id=&quot;7oit&quot;&gt;Practical Insights and Code Examples&lt;/h2&gt;
  &lt;p id=&quot;rUMk&quot;&gt;For developers, it is useful to see how AleoBFT’s steps map to code. A minimalist pseudocode snippet is shown above, but in production, Aleo uses &lt;strong&gt;snarkOS&lt;/strong&gt; (Rust) and &lt;strong&gt;snarkVM&lt;/strong&gt; (Rust) implementations. The on-chain logic for committees and blocks is written in Leo (the high-level ZK language), but the consensus engine runs at the network layer.&lt;/p&gt;
  &lt;p id=&quot;39zd&quot;&gt;As a concrete example, here is pseudocode for validating a certificate and possibly committing it&lt;/p&gt;
  &lt;pre id=&quot;Tnhd&quot;&gt;def handle_certificate(cert):
    # Verify signatures and endorsements
    if not cert.has_n_minus_f_signatures():
        return False
    for sig in cert.signatures:
        if not verify_signature(sig, cert.digest, signer=cert.author):
            return False
    # Insert into local DAG
    dag.insert(cert)
    # Vote: reference this cert in next round
    if cert.round % 2 == 0: 
        # Only anchors matter for commit
        votes = count_references(cert)  # how many next-round certs reference this one
        if votes &amp;gt;= (f+1):
            blockchain.append(cert)  # commit anchor
            return True
    return False
&lt;/pre&gt;
  &lt;p id=&quot;783B&quot;&gt;In practice, “count_references” would tally incoming edges (signatures in next-round certs) already present in the DAG. The function &lt;code&gt;verify_signature&lt;/code&gt; checks each validator’s signature; if any is invalid, the certificate is ignored entirely. This highlights one benefit of AleoBFT: most protocol logic (proposal validation, signature checking, vote counting) is simple and local. Validators do &lt;strong&gt;not need&lt;/strong&gt; to run complex leader-election or resolve forks; they simply follow deterministic rules on the DAG.&lt;/p&gt;
  &lt;p id=&quot;w1CB&quot;&gt;Blockchain developers can inspect Aleo’s reference implementation on GitHub (see snarkOS consensus code) and the formal spec PDF for precise algorithms. The Leo language makes it easy to integrate ZK logic with consensus steps: e.g., a contract can emit a transaction that triggers a stake change, and the validator software will include it in the next block as usual.&lt;/p&gt;
  &lt;h2 id=&quot;Qk4Y&quot;&gt;Conclusion&lt;/h2&gt;
  &lt;p id=&quot;bXYU&quot;&gt;AleoBFT is a state-of-the-art consensus protocol that blends DAG-based BFT (Narwhal/Bullshark) with zero-knowledge cryptography incentives. Formally modeled and proven, it guarantees safety under &amp;lt;1/3 Byzantine faults and provides instant finality, while its PoSW mechanism drives hardware innovation in proving. Against alternatives, AleoBFT achieves a unique balance: &lt;strong&gt;high throughput and fast finality (like BFT systems) with useful work incentives (unlike typical PoS)&lt;/strong&gt;. Its design is robust to a wide array of adversarial scenarios (Byzantine behavior, network asynchrony, eclipse attempts) thanks to its layered construction and formal underpinnings.&lt;/p&gt;
  &lt;p id=&quot;nMLd&quot;&gt;For developers, AleoBFT’s hybrid nature offers powerful guarantees: application code can assume finality after each block and rely on the privacy features of zk-SNARKs without worrying about forks or long reorganizations. At the same time, the consensus structure remains mathematically elegant, separating concerns (data availability vs ordering) in a way that has been proved correct. Aleo’s technical documentation and formal proofs are open for deep study, making it a rich platform for research and development. In sum, AleoBFT exemplifies the marriage of cutting-edge cryptography with proven distributed-systems theory, yielding both theoretical rigor and practical efficiency.&lt;/p&gt;
  &lt;p id=&quot;ak4E&quot;&gt;&lt;strong&gt;Write by alexanderblv for the Aleo, July 2025 &lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;KI1X&quot;&gt;&lt;strong&gt;&lt;a href=&quot;https://x.com/alexander_blv&quot; target=&quot;_blank&quot;&gt;x.com/alexander_blv&lt;/a&gt; &lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;ZD3E&quot;&gt;&lt;strong&gt;ERC20 - 0x1e1Aa06ff5DC84482be94a216483f946D0bC67e7&lt;/strong&gt;&lt;/p&gt;

</content></entry><entry><id>alexanderblv:aleo-state-model</id><link rel="alternate" type="text/html" href="https://teletype.in/@alexanderblv/aleo-state-model?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=alexanderblv"></link><title>Designing State in Aleo. Merkle-Tree Model for Records and Transaction Storage</title><published>2025-06-26T13:21:50.658Z</published><updated>2025-06-26T13:21:50.658Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img4.teletype.in/files/38/94/38940231-6ecf-4372-b7c1-206df331a1b7.png"></media:thumbnail><summary type="html">&lt;img src=&quot;https://img2.teletype.in/files/11/f0/11f095f9-0531-4148-a9fb-bace84572298.png&quot;&gt;Aleo’s innovative state model, which blends a UTXO-like record system with Merkle trees and zero-knowledge proofs for scalable, private smart contract execution. Each transaction in Aleo consumes and creates encrypted records, with their validity proven via SNARKs and Merkle membership proofs. The blockchain maintains a global Merkle root to ensure state integrity, enabling efficient verification and minimal on-chain data. Compared to Ethereum, Zcash, and Solana, Aleo offers a unique architecture that combines privacy, scalability, and verifiability — ideal for next-gen ZK applications.</summary><content type="html">
  &lt;p id=&quot;LftD&quot;&gt;Aleo adopts a &lt;strong&gt;record-based state model&lt;/strong&gt; (akin to UTXOs) rather than a plain account map. Each &lt;em&gt;record&lt;/em&gt; is an encrypted data object that represents application-specific state. When a transaction executes, records are consumed (spent) or created, updating the global state. To maintain integrity and privacy, Aleo tracks &lt;strong&gt;all record commitments&lt;/strong&gt; in a global Merkle-tree. Roughly speaking, each record &lt;code&gt;r = (v, pid, apk, d, ρ, r)&lt;/code&gt; (with visibility &lt;code&gt;v&lt;/code&gt;, program ID &lt;code&gt;pid&lt;/code&gt;, owner key &lt;code&gt;apk&lt;/code&gt;, data payload &lt;code&gt;d&lt;/code&gt;, random nonce &lt;code&gt;ρ&lt;/code&gt;, and randomness &lt;code&gt;r&lt;/code&gt;) is turned into a &lt;em&gt;commitment&lt;/em&gt;&lt;/p&gt;
  &lt;figure id=&quot;j1Iv&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/ef/0e/ef0e1859-e122-4863-8e40-64092a6e7a38.png&quot; width=&quot;370&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;RS6B&quot;&gt;which hides its contents. When the record is created in a transaction, its commitment &lt;code&gt;cm&lt;/code&gt; is &lt;em&gt;appended&lt;/em&gt; to the ledger; when the record is spent, its unique serial number&lt;/p&gt;
  &lt;figure id=&quot;Zawe&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/ff/97/ff97a3c6-8e20-4a3e-a733-d9aafe6b9546.png&quot; width=&quot;187&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;Pea7&quot;&gt;is revealed. The ledger (an append-only chain of blocks) maintains a global Merkle tree over all these commitments. The &lt;strong&gt;Merkle root&lt;/strong&gt; of this tree (the “state root”) is included in each block header. Thus, the block header succinctly summarizes the entire record state. For example, Aleo’s block header fields include &lt;code&gt;previous_state_root&lt;/code&gt; (the Merkle root of all prior records) and &lt;code&gt;transactions_root&lt;/code&gt; (the Merkle root of that block’s transactions) (see Figure below).&lt;/p&gt;
  &lt;figure id=&quot;Gy0I&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/6f/47/6f47894f-2cae-4ac7-9faf-3171be670fc7.png&quot; width=&quot;1000&quot; /&gt;
    &lt;figcaption&gt;&lt;em&gt;Aleo blocks chain together with each header containing a &lt;strong&gt;state root&lt;/strong&gt; (Merkle root of all record commitments so far) and a &lt;strong&gt;transactions root&lt;/strong&gt; (Merkle root of this block’s transactions).&lt;/em&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;RC9W&quot;&gt;Each Aleo &lt;strong&gt;transaction&lt;/strong&gt; carries its own proof of correct execution. In particular, an execution contains a &lt;em&gt;local state root&lt;/em&gt; ℓ_state that commits to all input/output records of that transaction. When the transaction is validated, the &lt;strong&gt;global state root&lt;/strong&gt; G_state (from the previous block) is updated by including the new records and removing spent ones, yielding the new state root published in the next block. Crucially, the transaction’s zero-knowledge proof must include &lt;em&gt;Merkle membership proofs&lt;/em&gt;: (1) that each &lt;strong&gt;input record’s commitment&lt;/strong&gt; is indeed in the prior Merkle tree, and (2) (conceptually) that each new &lt;strong&gt;output record commitment&lt;/strong&gt; will be inserted correctly. In practice, outputs need not prove &lt;em&gt;non&lt;/em&gt;-membership (they are new by construction), but inputs must prove membership in the existing state tree. These proofs ensure integrity without revealing record contents.&lt;/p&gt;
  &lt;p id=&quot;7H7D&quot;&gt;Aleo’s &lt;strong&gt;transaction verification&lt;/strong&gt; involves two inclusion checks (plus serial-number checks):&lt;/p&gt;
  &lt;ul id=&quot;Hsyq&quot;&gt;
    &lt;li id=&quot;ZQIT&quot;&gt;&lt;strong&gt;Local inclusion (ℓ_state):&lt;/strong&gt; Each transaction groups one or more &lt;em&gt;state transitions&lt;/em&gt; (function calls) and computes a local state root ℓ_state by hashing all its input and output commitments. Internally, verifying an execution requires checking that every input record’s commitment &lt;code&gt;cmi&lt;/code&gt; matches its authentic value and that &lt;code&gt;cmi&lt;/code&gt; is on the Merkle tree &lt;strong&gt;committed by ℓ_state&lt;/strong&gt;. In code this is like: &lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;wtYP&quot;&gt;&lt;code&gt;// For each input record ri with nonce ρi: sn_i = PRFSN(skPRF, ρi); // compute serial cm_i = CM.Commit(v || apk || d || ρi; r_i); assert(T.Verify(ℓ_state, cm_i, path_i) == 1); // Merkle proof w.r.t ℓ_state &lt;/code&gt; &lt;/p&gt;
  &lt;p id=&quot;2Lre&quot;&gt;This ensures the transaction internally “knows” its input records and that they hash up to ℓ_state.&lt;/p&gt;
  &lt;ul id=&quot;pd6Z&quot;&gt;
    &lt;li id=&quot;1HCJ&quot;&gt;&lt;strong&gt;Global inclusion (G_state):&lt;/strong&gt; Each input commitment must also be present in the &lt;strong&gt;global ledger tree&lt;/strong&gt; (the state up to the previous block). The transaction must provide a &lt;em&gt;ledger membership witness&lt;/em&gt; (a Merkle path) for every consumed &lt;code&gt;cmi&lt;/code&gt;. Formally, the prover shows for each input:&lt;/li&gt;
  &lt;/ul&gt;
  &lt;figure id=&quot;8230&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/fe/a4/fea4fa72-3a25-49d1-a136-a626f23a61c2.png&quot; width=&quot;266&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;dEar&quot;&gt;i.e. &lt;code&gt;cmi&lt;/code&gt; is a leaf in the global Merkle tree whose root is G_state. Meanwhile, revealing unique serial numbers &lt;code&gt;sn_i&lt;/code&gt; ensures no double-spend (the same record is not consumed twice). (Outputs, when published on-chain, automatically extend the state tree to the new root, so their “inclusion” is just by appending.)&lt;/p&gt;
  &lt;p id=&quot;s6wc&quot;&gt;In summary, &lt;strong&gt;Merkle proofs&lt;/strong&gt; in Aleo work much like in other blockchains: a record commitment is a leaf in a hash tree, and the path of hashes up to the root serves as a membership proof. If a protocol needed to prove &lt;em&gt;non&lt;/em&gt;-membership (that a record does &lt;em&gt;not&lt;/em&gt; exist), one could use a sparse-Merkle scheme: showing that at a given index the tree has only a default value. Aleo’s core model, however, mainly requires positive membership proofs for spent records. The effect is that miners (validators) need only verify O(log N) hashes per record, plus the SNARK, instead of scanning the whole state.&lt;/p&gt;
  &lt;h2 id=&quot;zyex&quot;&gt;Merkle Trees and Record Commitments&lt;/h2&gt;
  &lt;p id=&quot;hVcO&quot;&gt;Merkle trees are binary hash trees where each non-leaf node is &lt;code&gt;H(left ∥ right)&lt;/code&gt; and leaves are hashes of data (or commitments). In Aleo, when records are added to the ledger, their commitments &lt;code&gt;cm&lt;/code&gt; become new leaves. Figure [59] below illustrates a generic Merkle tree: transactions (or record commitments) at the bottom level are hashed pairwise, then those hashes are hashed again, and so on up to the &lt;strong&gt;Merkle root&lt;/strong&gt;. The Aleo block header stores exactly this root.&lt;/p&gt;
  &lt;figure id=&quot;bYgD&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/84/4a/844a7e0c-a312-4e22-a429-debd124f41ab.png&quot; width=&quot;1000&quot; /&gt;
    &lt;figcaption&gt;&lt;em&gt;A Merkle tree (hash tree) of transactions/records. Each leaf &lt;code&gt;T&lt;/code&gt; is hashed upward: pairwise hashing produces parent nodes, continuing until the single Merkle root. The root is stored in the block header.&lt;/em&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;4LVY&quot;&gt;Mathematically, if the leaf hashes are $h_0,h_1,\dots,h_{k-1}$, the tree computes pairs&lt;/p&gt;
  &lt;figure id=&quot;ag3O&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/09/9d/099d740e-8173-4edc-a106-614417eb9447.png&quot; width=&quot;359&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;Z6v2&quot;&gt;and so on; if the leaf count is odd, the last hash may be duplicated (as in Bitcoin) or treated specially. In Aleo, a &lt;em&gt;Poseidon&lt;/em&gt; hash (&lt;code&gt;Poseidon2::hash_to_field&lt;/code&gt;) is typically used under the hood for ZK SNARK friendliness. Concretely, the Merkle root for 8 leaves is built by hashing pairs, then those results, etc. The Merkle root provides a single 𝔽-field value that cryptographically binds &lt;em&gt;all&lt;/em&gt; leaves. In code one might write (in Leo) a function to verify a Merkle proof:&lt;/p&gt;
  &lt;pre id=&quot;RguX&quot;&gt;function verify_merkle(root: field, leaf: field, path: [field; d], index: u8) -&amp;gt; bool {
    // Recompute the root from the given leaf and sibling hashes.
    let mut hash = leaf;
    for i in 0..d {
        let sibling = path[i];
        // bit i of index tells whether hash is left or right child
        if ((index &amp;gt;&amp;gt; i) &amp;amp; 1u8) == 0u8 {
            hash = Poseidon2::hash_to_field(hash + sibling);
        } else {
            hash = Poseidon2::hash_to_field(sibling + hash);
        }
    }
    return hash == root;
}&lt;/pre&gt;
  &lt;p id=&quot;mqgQ&quot;&gt;This circuit checks that starting from &lt;code&gt;leaf&lt;/code&gt; and hashing with the provided &lt;code&gt;path&lt;/code&gt;, we recover the known &lt;code&gt;root&lt;/code&gt;. In Aleo, this kind of loop would be part of the SNARK proof logic. (In practice, Aleo’s proof equations like &lt;strong&gt;Rloc&lt;/strong&gt; and &lt;strong&gt;Rglb&lt;/strong&gt; formalize these checks.) Intuitively, proving membership is like proving you hold a valid VIP ticket in the tree of all tickets, without showing the ticket itself.&lt;/p&gt;
  &lt;p id=&quot;O9Hk&quot;&gt;Because the tree depth is $\approx\log_2(N)$, membership proofs cost $O(\log N)$ hashing time. This scales well even for millions of records. Updating the state (appending or removing leaves) can also be done in $O(\log N)$ time per change, e.g. by re-computing hashes on the path from a leaf to the root. Aleo’s validators batch many record updates per block: they “take all these record updates” and apply them to the Merkle tree in one go. This bulk-update yields a new &lt;em&gt;Merkle group&lt;/em&gt; or snapshot, whose root goes into the block. The FAQ explains this as allowing concurrent updates: the network can combine all parties’ diffs and then “patch up to a Merkle group” for the block header. In effect, Aleo regains some account-model concurrency (parallel state changes) while still using a UTXO-like record model.&lt;/p&gt;
  &lt;h2 id=&quot;fdVX&quot;&gt;Proving Membership in Transaction Verification&lt;/h2&gt;
  &lt;p id=&quot;XmP9&quot;&gt;In Aleo’s execution model, each transaction supplies all data needed to verify it, including two sets of Merkle proofs:&lt;/p&gt;
  &lt;ul id=&quot;q4gx&quot;&gt;
    &lt;li id=&quot;fI12&quot;&gt;&lt;strong&gt;Local State Proofs:&lt;/strong&gt; A transaction may consist of multiple function calls. The prover first hashes all inputs/outputs of those calls into a &lt;em&gt;local state commitment&lt;/em&gt; ℓ_state. The SNARK then enforces that each input record satisfies &lt;/li&gt;
  &lt;/ul&gt;
  &lt;figure id=&quot;u6kM&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/57/06/57069341-76ca-4899-a2ab-5ea3b46f986e.png&quot; width=&quot;240&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;hfxC&quot;&gt;where cic_i is the record commitment recomputed from the prover’s inputs and wi(ℓ)w^{(ℓ)}_{i} is the Merkle path from cic_i to ℓ_state. Similarly, each output commitment (representing a newly created record) must hash into the same local root ℓ_state. This binds the transaction internals into a single digest.&lt;/p&gt;
  &lt;ul id=&quot;1HVz&quot;&gt;
    &lt;li id=&quot;3PJu&quot;&gt;&lt;strong&gt;Global State Proofs:&lt;/strong&gt; To finalize the transaction, each &lt;em&gt;input&lt;/em&gt; commitment must also appear in the global ledger tree. The prover provides a &lt;em&gt;ledger membership witness&lt;/em&gt; (a path in the global Merkle tree) for every &lt;code&gt;c_i&lt;/code&gt;. The validator checks &lt;/li&gt;
  &lt;/ul&gt;
  &lt;figure id=&quot;fhUr&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/c5/bc/c5bc71e8-84f6-4eda-ba28-419dc351ff74.png&quot; width=&quot;407&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;nYFp&quot;&gt;where GstateG_{\text{state}} is the prior global root. In other words, the transaction proves it is “consuming” existing records. In addition, each input’s serial number &lt;code&gt;sn_i = PRF(sk_PRF, ρ_i)&lt;/code&gt; is verified unique and consistent. (This prevents double-spend: once a record’s &lt;code&gt;sn&lt;/code&gt; appears on-chain, that record cannot be used again.)&lt;/p&gt;
  &lt;p id=&quot;ApIF&quot;&gt;Concretely, a spend proof in the SNARK includes constraints like: parse each input record ri=(vi,apki,di,ρi,ri)r_i=(v_i,apk_i,d_i,ρ_i,r_i), compute&lt;/p&gt;
  &lt;figure id=&quot;7V6j&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/a3/47/a3470832-4514-4667-9202-849c6f944423.png&quot; width=&quot;590&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;9YwL&quot;&gt;then require &lt;code&gt;T.Verify(ℓ_state, c_i, w_i^(ℓ)) = 1&lt;/code&gt; and &lt;code&gt;L.Verify(G_state, c_i, w_i^(G)) = 1&lt;/code&gt;. Output records have similar commitments but need only prove consistency (they automatically extend the tree). The result is that a block’s transactions can be checked purely via public hashes: validators recompute the record commitments and verify the Merkle proofs against the known state root.&lt;/p&gt;
  &lt;p id=&quot;qUx5&quot;&gt;Finally, once a transaction is accepted, validators &lt;strong&gt;append&lt;/strong&gt; the new output commitments to the state. Each new block thus produces an updated global root. Any user can later obtain a Merkle proof for any recorded commitment using APIs (e.g. &lt;code&gt;getMerkleProof&lt;/code&gt;). This allows light clients to verify inclusion of a record in the public state without downloading everything.&lt;/p&gt;
  &lt;h2 id=&quot;sSxu&quot;&gt;Transaction History and Efficient State Access&lt;/h2&gt;
  &lt;p id=&quot;9pAm&quot;&gt;Aleo blocks record not only state roots but also the transaction list. Internally, the block body contains an array of transactions; the header stores the Merkle root of this array (the “transactions_root”). This means each block is itself a small Merkle tree: one can prove a transaction is in a block by its Merkle path. In practice, this lets explorers or relayers show inclusion or build SPV proofs for transactions.&lt;/p&gt;
  &lt;p id=&quot;0VIx&quot;&gt;Beyond Merkle trees, Aleo also uses &lt;em&gt;mappings&lt;/em&gt; (account-like tables) for certain applications. For example, if a program uses a global mapping (address → value), the ledger maintains a Merkle accumulator of that map’s entries. An update to a mapping entry (like an account transfer) likewise provides a membership proof against the old root and yields a new root via an &lt;code&gt;UpdateMappings&lt;/code&gt; operation. The state thus can mix UTXO-like records with key-value stores, each versioned by its root hash in the chain.&lt;/p&gt;
  &lt;p id=&quot;v2eZ&quot;&gt;&lt;br /&gt;Because Aleo is a ZK-VM, &lt;em&gt;all&lt;/em&gt; state is essentially off-chain data with on-chain commitments. Validators do not store plaintext; they only need to maintain the Merkle roots. To access data (e.g. read a record’s contents), a user typically queries full nodes. However, provers only need &lt;em&gt;random&lt;/em&gt; access (through the Merkle paths they include). The static Merkle structure allows fast lookup by index (if records have deterministic indices) or by searching the tree (in a sparse tree, index can be the hash of the commitment itself). In any case, membership proofs are $O(\log N)$ in size. For large-scale use, Aleo could employ techniques like partitioning the state (each program has its own tree) or storing Merkle forests for scaling. The existing specs suggest batching updates “in one go” to avoid constant tree rebalancing, which hints at a design like a state Merkle forest where each program’s records form a subtree, then combined.&lt;/p&gt;
  &lt;p id=&quot;Iu5C&quot;&gt;In terms of &lt;strong&gt;history&lt;/strong&gt;, Aleo also keeps an append-only ledger of all transactions and state roots. Because each transaction’s execution root ℓ_state is included in that transaction, one can reconstruct the state evolution by iterating the blocks. (Figure [62] in the spec shows a diagram of how transitions in transactions are “Merkilized” to produce ℓ_state, and then how ℓ_state’s are similarly combined per block.) For long-term pruning or light-clients, one could checkpoint roots. But fundamentally, Aleo archives everything in block history (block headers + Merkle roots + SNARK proofs).&lt;/p&gt;
  &lt;h2 id=&quot;Zfng&quot;&gt;Performance and Scalability Analysis&lt;/h2&gt;
  &lt;p id=&quot;SVkd&quot;&gt;&lt;strong&gt;Operation cost:&lt;/strong&gt; All Merkle operations in Aleo cost $O(\log N)$ hashes per record, where $N$ is the number of records in the state. Computing or verifying a Merkle root requires hashing along a tree branch of length $\approx\log_2(N)$. Poseidon hash (used by default) is efficient in SNARK circuits, so membership proofs are succinct (small, constant-size per proof) and fast to verify. Record insertion is similar: one must hash the new leaf with its sibling, and propagate up to the root, again $O(\log N)$. In practice, a block may contain many transactions each with multiple inputs, so validators may update thousands of leaves and recompute many paths each block. This is mitigated by &lt;strong&gt;batching&lt;/strong&gt;: since all updates in a block produce one new root, miners can aggregate updates and re-compute the tree once (e.g. using incremental Merkle-tree techniques). The FAQ implies exactly this: “the network can effectively take all these record updates, and then in one go, update a Merkle tree with everybody’s diffs... and patch up to a Merkle group”.&lt;/p&gt;
  &lt;p id=&quot;OTrq&quot;&gt;&lt;strong&gt;Latency:&lt;/strong&gt; The biggest bottleneck in Aleo is actually generating the zero-knowledge proof, which happens off-chain by either the user or a prover. The Merkle-check parts are light compared to the arithmetic-circuit part of the proof. Verification on-chain (by validators) is quite fast: besides a SNARK verify, only a few hash computations are needed. Aleo’s consensus (PoSW/AleoBFT) targets on-chain throughput of a few hundred TPS initially, but theoretical limits are much higher with optimized hardware. By comparison, &lt;strong&gt;Ethereum’s&lt;/strong&gt; world state uses a Merkle–Patricia Trie. That structure also offers $O(\log N)$ operations, but in practice it can be slower due to RLP-encoding overhead and larger node fanout. Aleo’s flat Merkle tree is simpler (binary, fixed structure) and easier to parallelize when batching.&lt;/p&gt;
  &lt;p id=&quot;PLKm&quot;&gt;&lt;strong&gt;Comparisons:&lt;/strong&gt;&lt;/p&gt;
  &lt;ul id=&quot;8jgW&quot;&gt;
    &lt;li id=&quot;p1Ni&quot;&gt;&lt;em&gt;Ethereum (Account State):&lt;/em&gt; Ethereum stores accounts in a global trie. Lookups/inserts are $O(\log N)$, but nodes have up to 16 branches (hexary trie), and modification requires updating all ancestor nodes. Aleo’s Merkle tree is binary (branch factor 2) and updates happen only on paths touched. Also, Ethereum’s state is plaintext on-chain (no ZK privacy), while Aleo’s commitments hide values. Ethereum’s future upgrade to Verkle trees promises faster updates (even batched), which is conceptually similar to Aleo’s approach.&lt;/li&gt;
    &lt;li id=&quot;uHpb&quot;&gt;&lt;em&gt;Zcash (Note Commitment Tree):&lt;/em&gt; Zcash’s shielded pool (Sprout/Sapling) also uses a Merkle tree of note commitments. Spending a note requires a Merkle proof that the note’s commitment is in the tree (and revealing a nullifier). This is very close to Aleo’s record model, except Zcash’s tree only grows (notes are never “removed”, they just become spent via nullifiers) and the proof system is specialized for transfers. Aleo generalizes this to arbitrary programs: any record, not just a currency note, sits in the tree. Both systems incur $O(\log N)$ costs for proofs. Zcash’s major overhead is in the SNARK proving time (spent-and-output spends), whereas Aleo also has SNARK cost but can batch multiple state transitions in one proof. (Note: Aleo uses Fiat-Shamir NIZKs, not just Groth16 zk-SNARKs.)&lt;/li&gt;
    &lt;li id=&quot;ozYs&quot;&gt;&lt;em&gt;Solana (Account Model, No Tree):&lt;/em&gt; Solana famously avoids any global Merkle structure for state. Each account’s state is stored as a flat key-value entry in a big memory-mapped database (Cloudbreak). Looking up an account is $O(1)$ (hashtable-like), and validators don’t compute state roots at runtime. This yields very high TPS (tens of thousands) but sacrifices on-chain verifiability of history. There is no on-chain Merkle root to audit (finality relies on checkpoints and signature voting instead). By contrast, Aleo trades some speed for &lt;em&gt;verifiable privacy&lt;/em&gt;: every change is double-checked by hash commitments and ZK proofs. In Solana, a transaction’s success means the runtime wrote to accounts; in Aleo, it also means “I proved I was allowed and correct.” Naturally, state size in Solana can grow huge and requires pruning, whereas in Aleo the global state is cryptographically compressed into roots (though full nodes still store the data).&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;zqxe&quot;&gt;&lt;strong&gt;Scalability:&lt;/strong&gt; Aleo’s Merkle-tree model scales logarithmically. As $N$ grows, proofs lengthen only by a few bits. Even if the network had millions of records, a membership proof is just ~20 hashes (if $N\sim10^6$). The heavy lifting remains the SNARK proof, which can be parallelized off-chain. On-chain storage is small: each block header stores only roots. By contrast, naive UTXO models require storing all UTXOs on-chain, and pure account models store the entire map state. Aleo’s hybrid (Merkle commitments + ZK proofs) is designed so that the &lt;strong&gt;on-chain footprint is minimal&lt;/strong&gt; (just hashes and proofs), and bulk data (encrypted records) lives off-chain or only in node databases.&lt;/p&gt;
  &lt;p id=&quot;8Z0z&quot;&gt;Finally, end-to-end latency and throughput depend also on consensus. Aleo’s novel PoSW/AleoBFT mixes proof-generation effort with BFT. In principle, many transactions (each with their own multi-transition proofs) can be batched into one block, and parallelized prover nodes can run SNARKs. The concurrency trick of merging Merkle-deltas means Aleo can handle high contention: many users updating disjoint records cause little conflict. This is an advantage over traditional account-based systems, where global locks (on the state tree) can serialize updates.&lt;/p&gt;
  &lt;h2 id=&quot;YOfZ&quot;&gt;Example: Leo Code Snippets&lt;/h2&gt;
  &lt;p id=&quot;NsNO&quot;&gt;Below is a simplified Leo snippet showing how one might check Merkle membership within a circuit (pseudocode):&lt;/p&gt;
  &lt;pre id=&quot;ev27&quot;&gt;program verify_record.aleo {
    // Verifies a record commitment is in the given Merkle root.
    function check_record(root: field, 
                          leaf_commitment: field, 
                          merkle_path: [field; 32], 
                          index: u8) -&amp;gt; bool {
        let computed = leaf_commitment;
        for i in 0..32u8 {
            let sib = merkle_path[i];
            if ((index &amp;gt;&amp;gt; i) &amp;amp; 1u8) == 0u8 {
                computed = Poseidon2::hash_to_field(computed + sib);
            } else {
                computed = Poseidon2::hash_to_field(sib + computed);
            }
        }
        // Ensure the recomputed root matches.
        assert(computed == root);
        true
    }
}&lt;/pre&gt;
  &lt;p id=&quot;lFZB&quot;&gt;This function iteratively rebuilds the Merkle root from a leaf and its siblings, bit by bit. In an actual Aleo circuit, &lt;code&gt;root&lt;/code&gt; would be either ℓ_state or G_state, and the assert would be part of the SNARK constraints. All arithmetic is in the finite field (Poseidon hash is circuit-friendly).&lt;/p&gt;
  &lt;p id=&quot;fr9r&quot;&gt;Another snippet: computing a record serial number (nullifier) in Leo:&lt;/p&gt;
  &lt;pre id=&quot;CM8U&quot;&gt;program serial.aleo {
    function compute_serial(sk_prf: field, rho: field) -&amp;gt; field {
        // PRF function for serial numbers (here using Poseidon as a stand-in).
        return Poseidon2::hash_to_field(sk_prf + rho);
    }
}&lt;/pre&gt;
  &lt;p id=&quot;Y5PY&quot;&gt;In reality Aleo uses a specific PRF circuit &lt;code&gt;PRF_{SN}&lt;/code&gt; for serials, but the idea is the same: the serial &lt;code&gt;sn&lt;/code&gt; is a deterministic hash of the secret key and nonce. The SNARK includes the constraint &lt;code&gt;sn == PRF_SN(sk_prf, ρ)&lt;/code&gt; to bind the prover’s knowledge of the spending key.&lt;/p&gt;
  &lt;h2 id=&quot;YEYh&quot;&gt;Conclusion&lt;/h2&gt;
  &lt;p id=&quot;yAHA&quot;&gt;Aleo’s state design blends the &lt;strong&gt;UTXO-style record model&lt;/strong&gt; with &lt;strong&gt;Merkle-accumulator proofs&lt;/strong&gt; to achieve verifiable, private state transitions. Every record’s commitment lives in a giant Merkle tree whose root is on-chain. Transaction validity is anchored by SNARKs and Merkle proofs, ensuring that &lt;em&gt;anything&lt;/em&gt; needed (membership of inputs, uniqueness of serials, correct commit of outputs) is checked without leaking state. This model scales logarithmically, can exploit parallel updates, and allows full auditability of historical state. In practice, it means Aleo validators and clients rely on very efficient hash computations and proofs, while the user’s view remains succinct (only roots and proofs). Compared to traditional blockchains, Aleo’s approach is like Bitcoin’s UTXO model on steroids: it generalizes UTXOs to arbitrary programs and encrypts them by default, but still enjoys the efficient lookup and proof-of-inclusion properties of a Merkle tree. The result is a state architecture that is both &lt;strong&gt;rigorous&lt;/strong&gt; (provably consistent) and &lt;strong&gt;scalable&lt;/strong&gt;, suited for private ZK applications without sacrificing performance.&lt;/p&gt;
  &lt;p id=&quot;ak4E&quot;&gt;&lt;strong&gt;Write by alexanderblv for the Aleo, June 2025 &lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;KI1X&quot;&gt;&lt;strong&gt;&lt;a href=&quot;https://x.com/alexander_blv&quot; target=&quot;_blank&quot;&gt;x.com/alexander_blv&lt;/a&gt; &lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;ZD3E&quot;&gt;&lt;strong&gt;ERC20 - 0x1e1Aa06ff5DC84482be94a216483f946D0bC67e7&lt;/strong&gt;&lt;/p&gt;

</content></entry><entry><id>alexanderblv:snarkvm</id><link rel="alternate" type="text/html" href="https://teletype.in/@alexanderblv/snarkvm?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=alexanderblv"></link><title>Microarchitecture of snarkVM. Analyzing the Virtual Machine for Zero-Knowledge Computations</title><published>2025-06-02T12:15:03.745Z</published><updated>2025-06-10T07:22:02.467Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img3.teletype.in/files/ee/59/ee5938ad-6543-4a93-b635-1e142e8c727c.png"></media:thumbnail><summary type="html">&lt;img src=&quot;https://img2.teletype.in/files/dc/b7/dcb7ace5-ae5d-4a0f-b8a3-166b49999ea9.png&quot;&gt;snarkVM is Aleo’s zero-knowledge virtual machine that runs Leo programs off-chain and generates zk-SNARKs to prove correctness—without revealing private data. It compiles code into R1CS circuits, enforces privacy via encrypted records, and integrates directly with Aleo’s ledger. Built in Rust, snarkVM enables powerful private apps like confidential finance and secure voting.</summary><content type="html">
  &lt;p id=&quot;N9gv&quot;&gt;Aleo’s &lt;strong&gt;snarkVM&lt;/strong&gt; (Aleo Virtual Machine) is the off-chain execution engine that powers privacy-preserving smart contracts on the Aleo blockchain. It enables developers to write arbitrary computations in the Leo language and have them executed off-chain, with succinct zk-SNARK proofs attesting to their correctness. In snarkVM, &lt;em&gt;all&lt;/em&gt; data inputs and outputs remain cryptographically private (i.e. &lt;em&gt;encrypted&lt;/em&gt; on-chain), while the fact that a given function was executed is public. The VM compiles high-level Leo code into arithmetic circuits (R1CS) that can be proved and verified. In this way, snarkVM lets anyone run arbitrary programs “in the dark,” revealing only a proof of correct execution and nothing about secret inputs.&lt;/p&gt;
  &lt;p id=&quot;W7gE&quot;&gt;snarkVM’s architecture is built around three core components: the &lt;strong&gt;synthesizer&lt;/strong&gt;, which translates programs into zk-friendly circuits; the &lt;strong&gt;proof algorithms&lt;/strong&gt;, which implement the underlying SNARK (Aleo uses Varuna, a Marlin-based proof system); and the &lt;strong&gt;ledger module&lt;/strong&gt;, which manages cryptographic accounts and integrates with the blockchain. At a high level, snarkVM implements the Zexe-based Decentralized Private Computation (DPC) model. It provides keys for each account (private view and compute keys, public address keys), an &lt;em&gt;Authorize&lt;/em&gt; phase where clients “sign” transition requests, an &lt;em&gt;Execute&lt;/em&gt; phase that builds arithmetic circuits and proofs, and a &lt;em&gt;Finalize&lt;/em&gt; phase that updates on-chain state. In this way, snarkVM ensures that every Leo program execution yields a succinct zk-SNARK proof of correctness, while raw values remain confidential.&lt;/p&gt;
  &lt;p id=&quot;dO1b&quot;&gt;Internally, snarkVM is a &lt;strong&gt;stack-based VM&lt;/strong&gt; and execution engine that gradually constructs R1CS constraints as it processes each instruction. Every Leo program is first compiled into a low-level &lt;em&gt;Aleo instructions&lt;/em&gt; assembly (by the Leo compiler) and then into AVM opcodes, which the VM consumes. The VM’s synthesizer module reads each instruction (e.g. arithmetic ops, logic, branching, cryptographic hashes) and instantiates corresponding SNARK &lt;em&gt;gadgets&lt;/em&gt; (constraint circuits). In effect, executing an instruction in snarkVM means “adding those constraints” to the proof. Once a function’s instructions are all processed, the VM has built a complete arithmetic circuit (Rank-1 Constraint System) representing the entire computation. A zk-proof (using Aleo’s Varuna/Marlin backend) is then generated for this circuit. Finally, the VM outputs the (encrypted) results along with the proof to be submitted on-chain.&lt;/p&gt;
  &lt;h2 id=&quot;5m85&quot;&gt;Leo Programs and the Compilation Pipeline&lt;/h2&gt;
  &lt;p id=&quot;fPvU&quot;&gt;Developers write &lt;strong&gt;Leo&lt;/strong&gt; programs using a Rust-like syntax. For example, consider a simple Leo program that adds two numbers:&lt;/p&gt;
  &lt;pre id=&quot;nv71&quot;&gt;// Example 1: A simple Leo transition that adds two public inputs.
program hello.aleo {
    transition main(public a: u32, b: u32) -&amp;gt; u32 {
        let c: u32 = a + b;
        return c;
    }
}&lt;/pre&gt;
  &lt;p id=&quot;4uUA&quot;&gt;This &lt;code&gt;hello.aleo&lt;/code&gt; declares a public &lt;code&gt;transition&lt;/code&gt; function &lt;code&gt;main(a, b)&lt;/code&gt; that returns &lt;code&gt;a + b&lt;/code&gt;. When compiled (e.g. via &lt;code&gt;leo run&lt;/code&gt; or &lt;code&gt;snarkvm new &amp;amp;&amp;amp; snarkvm build&lt;/code&gt;), the Leo compiler translates the code into the intermediate Aleo Instructions form (assembly). In the build output we see something like:&lt;/p&gt;
  &lt;pre id=&quot;yoBf&quot;&gt;Leo Compiled &amp;#x27;main.leo&amp;#x27; into Aleo instructions  
⛓  Constraints  
 • &amp;#x27;hello.aleo/main&amp;#x27; – 35 constraints (called 1 time)  
➔ Output  
 • 3u32  &lt;/pre&gt;
  &lt;p id=&quot;t4c3&quot;&gt;This tells us that calling &lt;code&gt;main&lt;/code&gt; once creates an R1CS with 35 constraints. Under the hood, each high-level operation (&lt;code&gt;+&lt;/code&gt;, variable assignment, return) became one or more R1CS constraints. The instructions might include ops like &lt;code&gt;add&lt;/code&gt; or &lt;code&gt;add.w&lt;/code&gt; (wrapping add), and checks like &lt;code&gt;assert.eq&lt;/code&gt; for final output. The full list of AVM opcodes (arithmetic, bitwise, branches, and even SHA hashes, Pedersen hashes, and BHP commitments) is documented in the Aleo docs. For our example, the pertinent opcodes would include an &lt;code&gt;add&lt;/code&gt; and possibly a cast or check for the returned value.&lt;/p&gt;
  &lt;p id=&quot;bUAF&quot;&gt;The compiler also generates a program manifest (&lt;code&gt;program.json&lt;/code&gt;) describing metadata and links to the entry point. Once compiled, running the program (&lt;code&gt;leo run&lt;/code&gt; or &lt;code&gt;snarkvm run&lt;/code&gt;) triggers snarkVM’s execution: it takes the Aleo instructions and processes them to produce a proof. The VM confirms all variables and operations satisfy the generated constraints. If successful, it emits a proof and the (encrypted) output. This proof can then be submitted to an Aleo node for on-chain verification. In other words, the &lt;strong&gt;full lifecycle&lt;/strong&gt; is: write Leo → compile to instructions → &lt;strong&gt;synthesize&lt;/strong&gt; into an R1CS circuit → prove → verify on-chain. The transformation from high-level Leo code to an arithmetic circuit is automated by the VM.&lt;/p&gt;
  &lt;p id=&quot;cVkt&quot;&gt;Behind the scenes, snarkVM’s &lt;strong&gt;synthesizer&lt;/strong&gt; reads the Aleo instructions and builds the zero-knowledge circuit. Each instruction translates into small “gadgets” that impose algebraic constraints on the program’s variables. Conceptually, an R1CS consists of matrices &lt;em&gt;A&lt;/em&gt;, &lt;em&gt;B&lt;/em&gt;, &lt;em&gt;C&lt;/em&gt; and a witness vector &lt;strong&gt;w&lt;/strong&gt; so that for each constraint row &lt;em&gt;i&lt;/em&gt;, 〈A_i, w〉 · 〈B_i, w〉 = 〈C_i, w〉. For example, an &lt;code&gt;add&lt;/code&gt; instruction like &lt;code&gt;c = a + b&lt;/code&gt; is enforced by constraints that bind &lt;em&gt;c&lt;/em&gt; to &lt;em&gt;a&lt;/em&gt; and &lt;em&gt;b&lt;/em&gt;. By chaining these across all instructions, the VM forms a system of quadratic constraints. After synthesis, snarkVM invokes the Varuna proof system to generate a succinct proof for the entire constraint system. This approach ensures that any Leo program, no matter how complex, is ultimately proven correct by demonstrating a valid R1CS witness.&lt;/p&gt;
  &lt;h2 id=&quot;ENu1&quot;&gt;The Aleo VM Execution Model&lt;/h2&gt;
  &lt;p id=&quot;kcAc&quot;&gt;Aleo’s VM design follows the Records-Nano Kernel (RNK) model of Zexe, with some twists. Each &lt;strong&gt;Transition&lt;/strong&gt; execution is split into stages. First, &lt;strong&gt;VM.Setup&lt;/strong&gt; initializes public parameters (for Pedersen/Hogd hash, signing schemes, etc.) and generates account keys. Next, &lt;strong&gt;VM.Authorize&lt;/strong&gt; is called by the user: this takes the account’s private key (&lt;code&gt;ask&lt;/code&gt;) and desired program function and inputs, and produces an &lt;em&gt;authorized request&lt;/em&gt; (basically a signed transition). Only after authorization can the VM attempt execution.&lt;/p&gt;
  &lt;p id=&quot;tu48&quot;&gt;In &lt;strong&gt;VM.Execute&lt;/strong&gt;, the snarkVM consumes a batch of authorized requests and the account’s compute key (&lt;code&gt;ack&lt;/code&gt;) to produce outputs. During execution, the VM follows its &lt;em&gt;stack machine&lt;/em&gt; semantics: it pushes values, pops registers, and handles control flow as dictated by the instructions. Crucially, as it executes each op, it &lt;em&gt;adds corresponding constraints&lt;/em&gt; to the growing circuit. By the end of the transition, VM.Execute outputs a new state update object and an on-chain &lt;strong&gt;proof&lt;/strong&gt; of correctness. If an instruction’s preconditions are violated (e.g. a range-check fails), execution aborts without affecting state. Otherwise, a succinct SNARK (via Varuna/Marlin) is generated attesting that &lt;em&gt;all&lt;/em&gt; constraints were satisfied by the secret inputs and intermediate computations.&lt;/p&gt;
  &lt;p id=&quot;Iclt&quot;&gt;After proof generation, &lt;strong&gt;VM.Finalize&lt;/strong&gt; is invoked on-chain by a validator or prover. It takes the proof (and any public “finalize inputs” from the transition) and checks the proof against the current ledger state. If valid, VM.Finalize updates persistent mappings on-chain (e.g. public ledger tables, if any) to reflect the transition’s outcome. In Aleo’s model, only &lt;em&gt;finalize inputs&lt;/em&gt; (a small public interface) touch the public state, while all private data remains off-chain. For example, a function might expose certain public outputs (or call a “finalize” function), but hidden values are consumed from encrypted records. The VM specification describes how a function’s private inputs produce (encrypted) records and how finalize inputs update the ledger.&lt;/p&gt;
  &lt;p id=&quot;paAn&quot;&gt;These stages are beautifully abstracted by the formal VM tuple &lt;strong&gt;(Setup, Authorize, Execute, Finalize, Synthesize, VfyExec, VfyDeploy)&lt;/strong&gt;. VM.Synthesize allows deployment of a new program to the network (publishing its circuit and metadata) and VM.VfyExec/VfyDeploy correspond to the verification of execution proofs and deployment proofs, respectively. This design cleanly separates off-chain computation (private, heavy) from on-chain consensus (light, public). In practice, a typical transaction is: &lt;em&gt;Client&lt;/em&gt; calls VM.Authorize with a transition, &lt;em&gt;Prover&lt;/em&gt; runs VM.Execute locally to get a proof, &lt;em&gt;Validator&lt;/em&gt; runs VM.Finalize with that proof on-chain. Throughout, the account keys and the zk-friendly instruction set ensure that only intended values flow through.&lt;/p&gt;
  &lt;h2 id=&quot;ASdn&quot;&gt;Instruction Set and Gadget Microarchitecture&lt;/h2&gt;
  &lt;p id=&quot;Tu72&quot;&gt;Aleo’s virtual machine uses a custom, zk-friendly ISA (Aleo Instructions) rather than the EVM’s. The opcode set includes standard integer/range operations (e.g. &lt;code&gt;add&lt;/code&gt;, &lt;code&gt;mul&lt;/code&gt;, &lt;code&gt;lt&lt;/code&gt;, &lt;code&gt;cast&lt;/code&gt;, etc.) and rich cryptographic primitives (e.g. SHA-256 rounds, Pedersen hashes, and various commitment schemes like &lt;code&gt;commit.bhp256&lt;/code&gt;). Every opcode is implemented as a gadget: a small R1CS fragment. A &lt;code&gt;branch.eq&lt;/code&gt; uses selection gadgets and conditional execution logic. Special opcodes like &lt;code&gt;rand.chacha&lt;/code&gt; integrate ChaCha-based randomness within the finalize scope. In effect, &lt;em&gt;each VM instruction corresponds to one or more constraint rows&lt;/em&gt;. This low-level design (an assembly-like language targeting R1CS) allows very fine-grained control of circuit structure, optimizing away unnecessary overhead and making the VM Turing-complete but fully arithmetized.&lt;/p&gt;
  &lt;pre id=&quot;3cPL&quot;&gt;Example Aleo instructions (conceptual):

add  a [u32] b  // c = a + b
assert.eq c 10  // assert c == 10
branch.eq a 0 L1  // if (a == 0) goto L1&lt;/pre&gt;
  &lt;p id=&quot;mCc7&quot;&gt;By structuring programs this way, snarkVM can apply optimizations and formal verification. In fact, Aleo’s developers are actively using theorem provers (ACL2) to verify that each instruction’s gadget correctly implements its semantic. The modular gadget approach also means the VM can optimize common patterns: e.g. combining multiply+add into a fused multiply-add gadget with fewer constraints, or simplifying boolean logic across branches.&lt;/p&gt;
  &lt;p id=&quot;2upZ&quot;&gt;Because snarkVM is privacy-first, it carefully manages secret data. All sensitive inputs and outputs exist only inside the prover’s execution. The on-chain world sees only &lt;em&gt;commitments&lt;/em&gt; and proofs. For example, account balances are stored as encrypted records. A transition may consume some input records and produce new output records, all still encrypted under the user’s view key. The VM uses cryptographic commitments extensively (e.g. Pedersen or BHP commitments) to tie hidden values to constraints. In practice, this means that even though the VM is evaluating an arithmetic program, the underlying values of inputs/outputs are not revealed—only hashes or commitments of them appear on-chain. Additionally, snarkVM leverages &lt;strong&gt;per-transition randomness and keys&lt;/strong&gt;: each transition uses a unique randomizer (part of &lt;code&gt;sk_SIG&lt;/code&gt;) to ensure unlinkability of records. This orchestration of keys, commitments, and proofs guarantees the &lt;em&gt;privacy&lt;/em&gt; of data while still allowing verifiers to be convinced of correct computation.&lt;/p&gt;
  &lt;h2 id=&quot;5oX0&quot;&gt;Unique Privacy-First Features&lt;/h2&gt;
  &lt;p id=&quot;cpcK&quot;&gt;Aleo’s VM introduces several innovations tailored for confidentiality and efficiency. First, by &lt;strong&gt;separating data privacy from function privacy&lt;/strong&gt;, Aleo avoids the complexity of hiding which program is running. The name (ID) of the transition being executed is public, so the VM does not need to generate ZK proofs that &lt;em&gt;also hide the function code&lt;/em&gt;. This tradeoff dramatically reduces circuit overhead. In fact, every Aleo program (transition) has a unique program ID visible on the ledger, but all of its &lt;em&gt;arguments and memory state stay encrypted&lt;/em&gt;. This means the prover can use optimized, non-universal circuits tailored to each program, without randomizing for function privacy.&lt;/p&gt;
  &lt;p id=&quot;wG0G&quot;&gt;Secondly, Aleo’s account model is inherently ZK-friendly. Instead of unencrypted UTXOs like Zcash, Aleo uses &lt;em&gt;records&lt;/em&gt; with a novel “2-in, 2-out” per-function-call model (from Zexe) but extended with &lt;strong&gt;stateful mappings&lt;/strong&gt;. In practice, this allows composition of private functions more flexibly. The VM finalizes by consuming input records and writing output records, which can include both new private outputs and public outputs to persistent mappings. The specification describes a “finalize scope” where a function’s public outputs update on-chain state. For example, a private voting contract can tally votes in private, then publish only the aggregate result in a finalize mapping. The VM’s instructions like &lt;code&gt;read_map&lt;/code&gt; or &lt;code&gt;write_map&lt;/code&gt; handle these persistent state changes while keeping other data hidden.&lt;/p&gt;
  &lt;p id=&quot;zmTq&quot;&gt;Another key feature is the &lt;strong&gt;use of Rust and safe systems programming&lt;/strong&gt;. The snarkVM is implemented in Rust, with strong typing for field elements, cryptography, and circuit builders. Its microarchitecture resembles a secure enclave: there is no unguarded “memory dump” of secret data. All intermediate variables exist either in the VM’s internal state (for the proving party) or are encoded as variables in the circuit. This design avoids side-channel leaks. The modular structure (synthesizer, circuit composer, CPU emulator) makes it auditable: indeed, third parties have formally verified the correctness of many gadget implementations to ensure no constraint-sketching bugs slip through.&lt;/p&gt;
  &lt;p id=&quot;1nNO&quot;&gt;Finally, snarkVM provides developer ergonomics uncommon in ZK platforms. The high-level Leo language has generics, arrays, loops, and conditional logic, making it easy to express complex algorithms. Underneath, the VM microarchitecture supports these features by including opcodes for loops/branches and a Verifier-friendly module system. In essence, the VM internal pipeline -- from Leo source to AVM opcodes to circuit to proof -- is fully transparent to the developer, yet every step is optimized for proving performance. The result is a virtual machine that is as powerful as the EVM in capability, but infused with ZK-nativeness at the core.&lt;/p&gt;
  &lt;p id=&quot;wUKR&quot;&gt;To summarize snarkVM’s privacy guarantees: &lt;em&gt;all&lt;/em&gt; private state is encrypted and only manipulable by the prover, &lt;em&gt;all&lt;/em&gt; proofs are non-interactive and succinct, and the chain only sees minimal public outputs. This is in stark contrast to traditional VMs. It means developers can build features like confidential asset swaps, private voting, or hidden health data queries, knowing that snarkVM’s architecture will enforce privacy cryptographically. The VM’s ledger component also tracks keys and commitments so that proofs automatically update balances and indexes correctly. In effect, snarkVM acts as a “backstage magician” orchestrating complex cryptographic protocols while presenting a simple programming model to developers.&lt;/p&gt;
  &lt;h2 id=&quot;mrEm&quot;&gt;Comparison with Other Blockchain VMs&lt;/h2&gt;
  &lt;p id=&quot;yExm&quot;&gt;It is instructive to contrast snarkVM’s architecture with other popular VMs:&lt;/p&gt;
  &lt;ul id=&quot;si8t&quot;&gt;
    &lt;li id=&quot;DqoP&quot;&gt;&lt;strong&gt;Ethereum’s EVM&lt;/strong&gt; – The EVM is a transparent, 256-bit word-based machine where every operation and piece of data is public. It has opcodes for arithmetic, logic, storage, etc., but no notion of zk-proofs. All computations execute on-chain deterministically, consuming gas. In contrast, snarkVM executes &lt;em&gt;off-chain&lt;/em&gt; and produces a proof; its opcodes are deliberately chosen to be ZK-friendly (e.g. field ops, range checks). Unlike the EVM, the Aleo VM has no gas cost model per se in its core design (proofs enforce correct consumption), and it natively supports private state via cryptography. In short, EVM prioritizes transparency and on-chain consensus, whereas snarkVM prioritizes privacy and off-chain verification.&lt;/li&gt;
    &lt;li id=&quot;U0Ix&quot;&gt;&lt;strong&gt;StarkWare’s Cairo/STARK VMs&lt;/strong&gt; – Cairo is a Turing-complete language/VM for STARK proofs. It uses an ARIcing Instruction Row (AIR) arithmetization with an explicit segmented memory model. Cairo’s architecture features a (relatively simple) CPU with memory segments and built-in range-check hacks, optimized for STARK proving. While both Cairo and snarkVM aim for general-purpose ZK computation, their microarchitectures differ. Cairo’s instruction set is built around its bleeding-edge STARK-friendly operations, and its memory is explicitly laid out for polynomial execution. snarkVM, by contrast, uses R1CS gadgets and a stack/register model; its memory model is implicit (variables become wires in the circuit). snarkVM’s VM also integrates a ledger and account model, whereas Cairo by itself is a pure computation engine (the StarkNet system adds an on-chain state separately). Both VMs demonstrate the trade-offs of ZK design: Cairo’s simplicity aids STARK proving, while snarkVM’s richer instruction set and cryptographic primitives leverage SNARK efficiency.&lt;/li&gt;
    &lt;li id=&quot;u5j4&quot;&gt;&lt;strong&gt;zkSync Era VM (zkEVM)&lt;/strong&gt; – zkSync’s Era VM is an Ethereum-compatible execution environment that uses zk-proofs. It strives to match the EVM instruction set and semantics so that Solidity code runs without changes, then proves the resulting state transition with a STARK-like system (Halo 2 / PLONK variants). Architecturally, the zkEVM must handle full 256-bit EVM arithmetic and Ethereum storage models, which adds complexity and overhead. By contrast, snarkVM does not aim to be EVM-compatible; it redefines the VM around Aleo’s native primitives. The upside is performance: snarkVM’s circuits are leaner for its domain, and it natively handles privacy. However, zkSync’s approach has wider language support (Solid ity) and interoperability with Ethereum. In terms of microarchitecture, zkSync’s VM is essentially the EVM bridged to a proof system, whereas snarkVM is a bespoke ZK-native machine with its own language and accounts.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h2 id=&quot;B3u6&quot;&gt;Mathematical Essence of Circuits&lt;/h2&gt;
  &lt;p id=&quot;Os13&quot;&gt;Under the hood, snarkVM translates code into a system of polynomial equations over a finite field. A typical Rank-1 Constraint System (R1CS) constraint looks like:&lt;/p&gt;
  &lt;figure id=&quot;BddV&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/ac/25/ac25ff45-f30d-4570-94ce-46cb8b90e1df.png&quot; width=&quot;266&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;KA7V&quot;&gt;The VM arranges each instruction so that its semantics are enforced by equations of this form. For example, ensuring $z = x + y$ can be encoded with coefficients that force $z - x - y = 0$ (since linear terms suffice for addition). More complex ops like multiplication use both sides of the rank-1 form. By assembling thousands of such constraints, snarkVM creates an enormous algebraic statement: “There exists a witness &lt;strong&gt;w&lt;/strong&gt; such that all these equations hold.” The zk-proof then convinces verifiers of this statement without revealing &lt;strong&gt;w&lt;/strong&gt;.&lt;/p&gt;
  &lt;p id=&quot;T0PF&quot;&gt;snarkVM also uses arithmetic on elliptic curves and pairings (hidden in Varuna/Marlin) to compress these equations into small proofs. For an expert dive, one can examine Aleo’s formal VM spec or source code; the main point is that the microarchitecture turns every line of Leo code into exact algebraic relations in a field. Tools like the ACL2 formalization prove that this translation is correct, giving high assurance.&lt;/p&gt;
  &lt;h2 id=&quot;DmQt&quot;&gt;Getting Started and Resources&lt;/h2&gt;
  &lt;p id=&quot;Hex4&quot;&gt;For developers intrigued by Aleo, the first step is to install the Leo compiler and snarkVM runtime (both written in Rust). Aleo’s developer documentation is comprehensive: the &lt;a href=&quot;https://developer.aleo.org/leo-learn&quot; target=&quot;_blank&quot;&gt;Leo language guide&lt;/a&gt; and &lt;a href=&quot;https://developer.aleo.org/guides/aleo/opcodes&quot; target=&quot;_blank&quot;&gt;Aleo instructions reference&lt;/a&gt; cover syntax and opcodes. The GitHub repository (provable/snarkVM) contains the source code and examples. To build a circuit manually, you can also use the low-level “Aleo instructions” language if you want fine-grained control. The Aleo team has also published an academic-spec VM paper and blog posts (e.g. Aleo’s technical deep-dives) explaining architecture and best practices. Exploring these will help you understand how your Leo code ultimately becomes a proof.&lt;/p&gt;
  &lt;p id=&quot;FHi8&quot;&gt;In summary, snarkVM’s microarchitecture is a carefully engineered pipeline optimized for zero-knowledge. It marries familiar VM concepts (stack machine, opcodes, functions) with cryptographic primitives and proof techniques. The elegance lies in how seamlessly high-level code yields a tiny proof: developers write normal-looking code, yet under the hood the VM is orchestrating FFT-friendly polynomials and constraint synthesizers. &lt;strong&gt;This fusion of compiler technology and cutting-edge cryptography is what makes Aleo’s platform both powerful and inspiring&lt;/strong&gt;. It demonstrates that privacy and programmability can coexist, and invites developers to experiment with entirely new kinds of applications — from confidential finance to private AI — all guaranteed by mathematical proofs. With snarkVM, Aleo shows that the future of computing can be both private and fully general.&lt;/p&gt;
  &lt;p id=&quot;ak4E&quot;&gt;&lt;strong&gt;Write by alexanderblv for the Aleo, June 2025 &lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;KI1X&quot;&gt;&lt;strong&gt;&lt;a href=&quot;https://x.com/alexander_blv&quot; target=&quot;_blank&quot;&gt;x.com/alexander_blv&lt;/a&gt; &lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;ZD3E&quot;&gt;&lt;strong&gt;ERC20 - 0x1e1Aa06ff5DC84482be94a216483f946D0bC67e7&lt;/strong&gt;&lt;/p&gt;

</content></entry><entry><id>alexanderblv:sp1-precompiles</id><link rel="alternate" type="text/html" href="https://teletype.in/@alexanderblv/sp1-precompiles?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=alexanderblv"></link><title>P3 - Precompiles in SP1. The Secret to Lightning-Fast Performance</title><published>2025-06-02T07:01:33.696Z</published><updated>2025-06-02T07:01:33.696Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img3.teletype.in/files/ad/be/adbe940e-b23e-4ad0-ba3b-2b7a0da66c45.png"></media:thumbnail><summary type="html">&lt;img src=&quot;https://img1.teletype.in/files/8c/66/8c664a1e-45ab-4c1a-8d53-3a1475635fc9.png&quot;&gt;SP1 supercharges zero-knowledge proofs using precompiles — optimized syscalls for heavy operations like hashing and elliptic curve math, making them up to 1000× faster. This article explains how SP1 uses ecall to tap into specialized circuits and why its modular design makes it one of the most efficient zkVMs today.</summary><content type="html">
  &lt;p id=&quot;2bJC&quot;&gt;Welcome back to the wacky world of SP1! In our last adventure, we followed how your Rust code gets magically transformed into a cryptographic proof (yep, &lt;strong&gt;Part 2&lt;/strong&gt; was a wild ride through compilers and proofs). Now, buckle up for &lt;strong&gt;Part 3&lt;/strong&gt;, where we unveil SP1’s secret sauce for &lt;em&gt;speed&lt;/em&gt;. Spoiler alert: it involves cheat codes, fast lanes, and maybe a secret elevator or two. Say hello to &lt;strong&gt;precompiles&lt;/strong&gt;, the trick that makes heavy operations in Succinct’s SP1 zkVM &lt;em&gt;blazing&lt;/em&gt; fast. If you thought proving a program correct was cool, wait until you see how SP1 &lt;em&gt;optimizes&lt;/em&gt; that process for those computationally expensive bits. &lt;/p&gt;
  &lt;h2 id=&quot;oGG6&quot;&gt;What the Heck Are Precompiles (and Why Should You Care)?&lt;/h2&gt;
  &lt;p id=&quot;bjyS&quot;&gt;Imagine you&amp;#x27;re playing a video game and there’s a particularly tedious boss battle ahead. But you’ve got a cheat code that lets you skip the boss fight entirely and still claim the treasure. In the realm of SP1 (Succinct’s zkVM), &lt;strong&gt;precompiles&lt;/strong&gt; are kind of like those cheat codes for your program’s hardest computations. They let you &lt;em&gt;bypass&lt;/em&gt; the slow, step-by-step grind and jump to the answer as if by magic.&lt;/p&gt;
  &lt;p id=&quot;7wYP&quot;&gt;Okay, more technically: a &lt;em&gt;precompile&lt;/em&gt; is a pre-built, highly optimized implementation of a specific operation inside the VM. Instead of executing potentially thousands of normal instructions to, say, hash a piece of data or do big fancy math on an elliptic curve, SP1 can execute one &lt;em&gt;precompile instruction&lt;/em&gt; that achieves the same result &lt;em&gt;way&lt;/em&gt; faster. Think of it as a &lt;strong&gt;fast lane&lt;/strong&gt; on the highway for certain tasks – while regular instructions might crawl through traffic light by light, a precompile zips down the express lane and gets there in record time. &lt;/p&gt;
  &lt;p id=&quot;Qduf&quot;&gt;The term “precompile” actually comes from the Ethereum world, where &lt;em&gt;precompiled contracts&lt;/em&gt; are built-in routines for expensive operations (like signature verification or hashing) that run faster than if you wrote them in Solidity. SP1 borrows this idea but applies it inside a zkVM. Essentially, the designers of SP1 looked at all those heavy operations that programs often need – cryptographic hashing, elliptic curve arithmetic, signature checks, you name it – and said, &lt;em&gt;“What if we bake these directly into the VM as super-efficient primitives?”&lt;/em&gt;. The result is a &lt;em&gt;precompile-centric architecture&lt;/em&gt;, meaning SP1 is built around the idea of handling common expensive operations via special shortcuts rather than letting them bog down the normal instruction stream.&lt;/p&gt;
  &lt;p id=&quot;o0I6&quot;&gt;Why should you care? Because these shortcuts make a &lt;strong&gt;huge&lt;/strong&gt; difference in performance. Without precompiles, if your Rust program needed to compute a SHA-256 hash, the poor VM would have to chug through the entire hashing algorithm in software, one instruction at a time – that&amp;#x27;s dozens of rounds, a lot of bit-twiddling, and potentially &lt;em&gt;thousands&lt;/em&gt; of RISC-V instructions to prove. With a precompile, SP1 does the same hash in a single stroke, like a master painter with one swift move of the brush. The zkVM doesn’t have to prove all those intermediate steps; it just proves “I ran the hash function and here’s the result” with a dedicated circuit that’s &lt;em&gt;way&lt;/em&gt; more efficient at this task. Fewer steps to prove means less work for the prover and faster results for you. In fact, SP1’s precompile “cheat codes” can speed up certain operations by orders of magnitude – we’re talking 10×, 20×, even 1000× faster in some cases. It’s the difference between crawling through a marathon and taking a jet plane to the finish line. &lt;/p&gt;
  &lt;h2 id=&quot;K8I9&quot;&gt;Under the Hood. Fast Lanes via RISC-V Syscalls (ECALL)&lt;/h2&gt;
  &lt;p id=&quot;KNZ9&quot;&gt;So how does one invoke these magical precompiles? Do you wave a wand, chant some incantation? Not quite – you make a &lt;strong&gt;system call&lt;/strong&gt;. If you’re not familiar with system calls (syscalls): think of them as a program’s way of asking the “operating system” to do something on its behalf. In RISC-V (the instruction set SP1 uses), there’s a special instruction called &lt;code&gt;ecall&lt;/code&gt; (environment call). Normally, if a regular program running on an OS executes an &lt;code&gt;ecall&lt;/code&gt;, it’s like raising a hand and saying, “Hey OS, I need to do something privileged (like read a file or allocate memory) – please do it for me.” The OS then takes over, does the job, and returns control to the program.&lt;/p&gt;
  &lt;p id=&quot;JLGY&quot;&gt;In SP1’s case, there is no traditional operating system, but SP1 itself steps in to handle these calls. The clever trick is: &lt;strong&gt;precompiles are exposed as syscalls&lt;/strong&gt; inside the zkVM. When your SP1 guest program executes an &lt;code&gt;ecall&lt;/code&gt;, it’s essentially dialing a special number to invoke a precompile. Each precompile has its own unique syscall ID – like a phone extension for the specific “service” you want. For example, one ID might represent the SHA-256 hash extender operation, another might be for an elliptic curve multiplication, another for verifying a digital signature.&lt;/p&gt;
  &lt;p id=&quot;pNg6&quot;&gt;Here&amp;#x27;s a fun analogy: picture SP1 as having a bunch of &lt;em&gt;secret elevators&lt;/em&gt; hidden behind the normal doors. When your program reaches a heavy operation, it can press a special button (the syscall) to open one of these secret elevator doors. The elevator (SP1’s internal handler) whisks the computation away to a specialized floor (a custom circuit) where the operation is performed at lightning speed, and then drops you back into the normal flow. Meanwhile, from the outside, it just looked like you stepped in and out, and suddenly that huge task was done! No need to take the stairs step-by-step.&lt;/p&gt;
  &lt;p id=&quot;oNJO&quot;&gt;Under the hood, when &lt;code&gt;Opcode::ECALL&lt;/code&gt; is encountered during SP1’s execution, the VM peeks at a register (x5 in SP1’s calling convention) to see which syscall ID was requested. That ID tells SP1 &lt;em&gt;which&lt;/em&gt; precompile to execute. At that moment, SP1 invokes the special precompile logic instead of continuing with regular RISC-V instructions. Essentially, the VM says, “Aha, I recognize this request – let’s handle it in turbo mode!” It then runs the precompile’s custom circuit logic for that operation. Once that’s done, the VM comes back to earth and resumes normal instruction processing after the &lt;code&gt;ecall&lt;/code&gt;. From the perspective of your program, it’s as if a single instruction did a whole bunch of work – because, well, it did.&lt;/p&gt;
  &lt;h2 id=&quot;JAUK&quot;&gt;Precompiles in Action. Calling a Precompile from Rust&lt;/h2&gt;
  &lt;p id=&quot;gX8E&quot;&gt;Enough talk – let’s see how you, as a developer, would actually call one of these precompiles in your Rust code. Thanks to SP1’s Rust support, using a precompile feels almost like calling a normal function (just with a sprinkle of unsafe sugar and a dash of assembly under the hood). The SP1 SDK exposes these syscalls via extern &amp;quot;C&amp;quot; function declarations. For example, here’s a snippet from SP1’s library that declares a few syscalls:&lt;/p&gt;
  &lt;pre id=&quot;HgLm&quot;&gt;extern &amp;quot;C&amp;quot; {
    /// Halts the program with the given exit code.
    pub fn syscall_halt(exit_code: u8) -&amp;gt; !;
    /// Writes the bytes in the given buffer to a file descriptor (like stdout).
    pub fn syscall_write(fd: u32, write_buf: *const u8, nbytes: usize);
    /// Reads bytes from a file descriptor into a buffer.
    pub fn syscall_read(fd: u32, read_buf: *mut u8, nbytes: usize);
    /// Executes the SHA-256 extend operation on the given 64-word array.
    pub fn syscall_sha256_extend(w: *mut [u32; 64]);
    // ... and so on for other precompiles ...
}&lt;/pre&gt;
  &lt;p id=&quot;d9h1&quot;&gt;Those are some of the interfaces available to a program running on SP1. The &lt;code&gt;syscall_halt&lt;/code&gt;, &lt;code&gt;write&lt;/code&gt;, and &lt;code&gt;read&lt;/code&gt; resemble typical OS calls (e.g. for exiting or I/O), but notice &lt;code&gt;syscall_sha256_extend&lt;/code&gt; – that one is pure precompile goodness. It’s an interface to a part of the SHA-256 hashing process (specifically, the message schedule extension) that SP1 can handle via a custom circuit.&lt;/p&gt;
  &lt;p id=&quot;72hn&quot;&gt;How do we actually &lt;em&gt;use&lt;/em&gt; this in code? Pretty straightforward. Suppose you have an array of 64 &lt;code&gt;u32&lt;/code&gt; words that you want to run through SHA-256’s message schedule (if that sounds like arcane wizardry, it’s basically a sub-step of computing a SHA-256 hash). You’d do something like:&lt;/p&gt;
  &lt;pre id=&quot;s8GW&quot;&gt;// Prepare a 64-element array (perhaps part of a SHA-256 block)
let mut words: [u32; 64] = original_block_into_words();

// Call the SP1 SHA-256 extend precompile to process this array.
// (unsafe is required because we’re dealing with a raw pointer and an external call)
unsafe {
    syscall_sha256_extend(&amp;amp;mut words);
}
// After this call, &amp;#x60;words&amp;#x60; now contains the extended message schedule as per SHA-256 spec.&lt;/pre&gt;
  &lt;p id=&quot;vhuh&quot;&gt;And voilà – with that one call, SP1 just performed what would have been a whole lot of bit math under the hood. Notice we didn’t have to implement the SHA-256 algorithm ourselves or loop over rounds; SP1’s precompile handled it in one go. The &lt;code&gt;unsafe&lt;/code&gt; block is there because we’re calling an external C function and manipulating a raw pointer (Rust needs you to be explicit when you do things that could be dangerous), but in practice this is as easy as calling any other function. The heavy lifting is all on SP1.&lt;/p&gt;
  &lt;p id=&quot;BEWP&quot;&gt;What actually happened when you called &lt;code&gt;syscall_sha256_extend&lt;/code&gt;? Behind the scenes, that function is marked with &lt;code&gt;#[no_mangle]&lt;/code&gt; and uses some inline assembly to trigger the &lt;code&gt;ecall&lt;/code&gt; instruction with the right syscall number for the SHA-256 precompile. You didn’t see it, but your program essentially did &lt;code&gt;ecall&lt;/code&gt; and TOLD SP1, “Hey, do the SHA-256 extension thing for me, please.” SP1 caught that request and executed the highly optimized SHA-256 extension circuit instead of making you do it manually. Pretty cool, right?&lt;/p&gt;
  &lt;p id=&quot;osFT&quot;&gt;If you’re curious to see more examples, the SP1 repository on GitHub is full of goodies. There are example programs in the &lt;a href=&quot;https://github.com/succinctlabs/sp1/tree/main/examples&quot; target=&quot;_blank&quot;&gt;&lt;strong&gt;examples&lt;/strong&gt; folder&lt;/a&gt; that demonstrate various syscalls and precompiles in action. And of course, Succinct’s &lt;a href=&quot;https://www.succinct.xyz/developers&quot; target=&quot;_blank&quot;&gt;developer portal&lt;/a&gt; and the &lt;a href=&quot;https://succinctlabs.github.io/sp1/&quot; target=&quot;_blank&quot;&gt;SP1 documentation site&lt;/a&gt; have more details on available precompiles and how to use them. You’re basically getting to write high-level Rust code, and with a little &lt;code&gt;unsafe&lt;/code&gt; pixie dust, you invoke insanely efficient ZK circuits under the hood. Talk about &lt;em&gt;having your cake and eating it&lt;/em&gt; – you get both convenience &lt;strong&gt;and&lt;/strong&gt; performance.&lt;/p&gt;
  &lt;h2 id=&quot;T87k&quot;&gt;Why Precompiles Make Proofs So Much Faster&lt;/h2&gt;
  &lt;p id=&quot;9Cvu&quot;&gt;By now you might be thinking, “Alright, I get that precompiles are like shortcuts. But how exactly do they make things &lt;em&gt;so&lt;/em&gt; much faster? And how much faster are we talking?” Great questions! The secret lies in the nature of zero-knowledge proof performance: it largely depends on how many low-level operations (constraints) you have to prove.&lt;/p&gt;
  &lt;p id=&quot;viGf&quot;&gt;When SP1 executes a normal RISC-V instruction, the proving system has to enforce all the tiny details of that execution (registers updated correctly, memory accesses, etc.). If a task takes 10,000 RISC-V instructions, that’s 10,000 little steps the prover has to account for. But if the same task is done as a single precompile, the prover only has to account for &lt;strong&gt;one&lt;/strong&gt; high-level step (albeit a complex one) in a specialized circuit. It’s like the difference between checking every single arithmetic step of a long division problem versus just checking the final answer with a quick multiplication. The latter is drastically less work.&lt;/p&gt;
  &lt;p id=&quot;spwS&quot;&gt;Дизайн SP1 извлекает выгоду из этого большого времени. Для таких операций, как хеширование и математика эллиптических кривых, разница астрономическая. Возьмем наш пример SHA-256: без предварительной компиляции вы бы доказали каждое из 64 расширений сообщений, каждый раунд смешивания битов и т. д., что привело бы, возможно, к тысячам ограничений. С предварительной компиляцией SP1 вместо этого использует специальную схему SHA-256, которой может потребоваться только доказать несколько проверок (например, «эти выходные данные являются правильным расширением SHA-256 этих входных данных»). Количество ограничений (и, следовательно, работа доказывающего) уменьшается на порядки. В одном измеренном случае использование предварительной компиляции для проверки доказательства SNARK внутри SP1 сделало это примерно в 20 раз быстрее , чем делать это наивным способом. Это не 20% — это ускорение на 2000%! В вычислительных терминах это разница между ожиданием целой минуты и всего лишь тремя секундами.&lt;/p&gt;
  &lt;p id=&quot;chGg&quot;&gt;And that’s just one example. The folks at Succinct Labs have reported overall proof generation being up to &lt;strong&gt;10× cheaper&lt;/strong&gt; (in terms of computational resources) and execution about &lt;strong&gt;28× faster&lt;/strong&gt; compared to older-generation zkVMs, thanks in large part to this precompile-centric approach. In other words, SP1’s “fast lanes” aren’t just a little faster – they’re game-changing. Most real-world zk applications (think rollups, light clients, cross-chain bridges) spend most of their time on exactly these kinds of repetitive crypto operations. By turbocharging those, SP1 dramatically cuts down the overall proving time for practical use-cases.&lt;/p&gt;
  &lt;p id=&quot;rlhm&quot;&gt;To put it cheekily: SP1 on precompiles is like a caffeinated squirrel 🐿️ on a sugar rush, blitzing through workloads that would make a normal VM collapse from exhaustion. Meanwhile, you as the developer don’t break a sweat – you just call a function and let SP1 do its hyper-optimized thing. The bottom line is that precompiles take what would be &lt;em&gt;impossibly slow&lt;/em&gt; ZK computations and make them not just feasible, but downright snappy.&lt;/p&gt;
  &lt;h2 id=&quot;Tvmg&quot;&gt;Extensible Superpowers. Adding New Precompiles&lt;/h2&gt;
  &lt;p id=&quot;nNX0&quot;&gt;One of the coolest aspects of SP1’s architecture is that these “cheat code” precompiles aren’t a fixed set – they’re designed to be &lt;strong&gt;extensible&lt;/strong&gt;. SP1 was built with the foresight that new algorithms and heavy operations will always pop up in the future, especially in the rapidly evolving blockchain and zero-knowledge world. So, rather than hard-coding a few tricks and calling it a day, SP1 allows developers (yes, that could be &lt;em&gt;you&lt;/em&gt;!) to add new precompiles as needed, almost like plugging in a new expansion pack to a game.&lt;/p&gt;
  &lt;p id=&quot;yWA8&quot;&gt;How is this possible? Remember how we said each precompile is like an extra circuit or “table” alongside the main CPU logic? The SP1 framework is modular enough that you can introduce a new table for a new operation and hook it into the syscall mechanism. In practice, adding a precompile involves assigning it a new syscall ID, writing the Rust side to expose it (the extern function and the inline &lt;code&gt;ecall&lt;/code&gt; call), and implementing the circuit logic to support it. It’s definitely a bit of advanced work (you’re basically extending the VM’s instruction set), but the point is: &lt;strong&gt;the system is built to accommodate it&lt;/strong&gt;. The CPU doesn’t need to know the gritty details – it just knows that when it sees a certain &lt;code&gt;ecall&lt;/code&gt; ID, it should defer to the specialized circuit.&lt;/p&gt;
  &lt;p id=&quot;4zyK&quot;&gt;This design is a direct evolution of ideas from other zkVMs. For instance, Starkware’s Cairo VM introduced the notion of “builtins” – essentially extra built-in functionalities (like hashers) to speed things up in their VM. The downside there was that builtins were somewhat entangled in the core VM logic, making it hard to add new ones beyond what the VM originally shipped with. SP1’s precompile-centric approach takes that concept and makes it &lt;em&gt;easily extensible&lt;/em&gt;. It’s like having a modular synthesizer: you can always jack in a new effect module without redesigning the whole system. Want a new cryptographic algorithm supported? You can write a new precompile for it, slot it in, and voila – all SP1 programs can now call your super-speedy implementation of that algorithm.&lt;/p&gt;
  &lt;p id=&quot;KEXU&quot;&gt;Succinct Labs has embraced this openness. SP1 is fully open-source (&lt;a href=&quot;https://github.com/succinctlabs/sp1&quot; target=&quot;_blank&quot;&gt;GitHub repo&lt;/a&gt; if you want to peek or contribute) and they actively encourage the community to contribute new precompiles or improvements. In fact, teams out in the wild are already building on SP1’s flexibility – for example, some projects created custom precompiles for their specific use-cases (imagine adding a tailor-made circuit for your project&amp;#x27;s special math, achieving massive speedups). The core team has literally said they welcome adding precompiles for commonly used operations. So this isn’t a closed list of “official cheat codes” – it’s an open invitation to invent new ones as needed.&lt;/p&gt;
  &lt;p id=&quot;C3xY&quot;&gt;As of now, SP1 comes with an impressive suite of precompiles covering most of the “greatest hits” in cryptography. We’re talking hashing functions like &lt;strong&gt;SHA-256&lt;/strong&gt; and &lt;strong&gt;Keccak-256&lt;/strong&gt;, signature algorithms like &lt;strong&gt;Secp256k1&lt;/strong&gt; (hey there, Ethereum signatures) and &lt;strong&gt;Ed25519&lt;/strong&gt;, and elliptic curve operations on &lt;strong&gt;bn254&lt;/strong&gt; and &lt;strong&gt;bls12-381&lt;/strong&gt; (the math engines behind many zk-SNARKs and blockchain protocols). They even have precompiles for pairing operations on those curves, which are crucial for verifying certain proofs, making SP1 the first production zkVM to roll out such comprehensive crypto support. This means out-of-the-box, you have a whole arsenal of optimized cryptographic routines at your disposal. And if something’s missing today, there’s a pathway to add it tomorrow. Future-proofing?&lt;/p&gt;
  &lt;h2 id=&quot;AIO9&quot;&gt;Wrapping Up and Looking Ahead&lt;/h2&gt;
  &lt;p id=&quot;Ca2m&quot;&gt;By now, you should have a solid grasp of why precompiles are the secret to SP1’s lightning-fast performance. They’re the &lt;em&gt;cheat codes&lt;/em&gt; that let SP1 “prove” heavy computations without breaking a sweat, the &lt;em&gt;fast lanes&lt;/em&gt; that bypass traffic, the &lt;em&gt;secret elevators&lt;/em&gt; that zoom past hundreds of floors of work in one ride. For developers, they offer a beautiful abstraction: you write normal code, and SP1 quietly swaps in a rocket engine when it hits a hard part. The end result? Your zero-knowledge proofs generate in a fraction of the time, and your application can do more complex stuff without getting bogged down.&lt;/p&gt;
  &lt;p id=&quot;vWHH&quot;&gt;This third leg of our journey showed how SP1 isn’t just about &lt;em&gt;making things possible&lt;/em&gt; (running Rust code in a zkVM), it’s about making them &lt;strong&gt;practical and efficient&lt;/strong&gt;. Performance matters, especially if you want to use ZK tech in the real world, and SP1’s precompiles are a big reason why you won’t be left twiddling your thumbs waiting for proofs.&lt;/p&gt;
  &lt;p id=&quot;ttgr&quot;&gt;But we&amp;#x27;re not done yet! There’s one more exciting chapter coming up. We’ve seen how SP1 handles single programs at warp speed, but what about scaling out and handling many calls or big computations? In the next article, we’ll explore how SP1 uses &lt;strong&gt;shared calls&lt;/strong&gt; to tackle scalability – think of it as SP1’s way of doing teamwork, proving multiple things in parallel without breaking a sweat. Ever wondered how a zkVM might juggle &lt;em&gt;shards&lt;/em&gt; of computation or have different parts of a program proved simultaneously? That’s where we’re headed. It’s the grand finale where SP1 shows off its ability to scale like a champ. So stay tuned for &lt;strong&gt;Part 4&lt;/strong&gt;, where SP1 turns the dial to eleven on scalability and we discover what “shared calls” are all about (hint: it’s going to tie everything together in our zkVM adventure). Until next time, keep those cheat codes handy and your computations succinct!&lt;/p&gt;
  &lt;p id=&quot;ak4E&quot;&gt;&lt;strong&gt;Write by alexanderblv for the Succinct, June 2025 &lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;KI1X&quot;&gt;&lt;strong&gt;&lt;a href=&quot;https://x.com/alexander_blv&quot; target=&quot;_blank&quot;&gt;x.com/alexander_blv&lt;/a&gt; &lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;ZD3E&quot;&gt;&lt;strong&gt;ERC20 - 0x1e1Aa06ff5DC84482be94a216483f946D0bC67e7&lt;/strong&gt;&lt;/p&gt;

</content></entry><entry><id>alexanderblv:aleo-snarks</id><link rel="alternate" type="text/html" href="https://teletype.in/@alexanderblv/aleo-snarks?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=alexanderblv"></link><title>Elliptic Curves in Aleo. From BLS12-377 to Record Encoding</title><published>2025-05-26T05:43:21.908Z</published><updated>2025-05-26T05:43:21.908Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img4.teletype.in/files/76/9b/769b4ab7-ec5c-4eb5-891e-7dddc9ac8b79.png"></media:thumbnail><summary type="html">&lt;img src=&quot;https://img1.teletype.in/files/89/a7/89a7c8e5-eca0-4085-be70-8989b6c22c70.png&quot;&gt;Aleo is a privacy-first layer-1 blockchain where general-purpose programs execute off-chain with zero-knowledge proofs, and only concise proofs and encrypted records are published on-chain. At its core is a dual-curve SNARK system: BLS12-377 for efficient pairing-based proofs and BW6-761 for recursive verification. Records use elliptic curve encryption over an Edwards form of BLS12-377, ensuring that data like addresses and balances remain private unless marked public. Transactions operate over a UTXO-like model with commitments and serial numbers to prevent double spends. Aleo programs are written in Leo, with privacy built into the type system and cryptography handled under the hood.</summary><content type="html">
  &lt;p id=&quot;qg4n&quot;&gt;Aleo is a privacy-first layer-1 blockchain where general-purpose programs execute off-chain with zero-knowledge proofs, and only concise proofs and &lt;strong&gt;encrypted records&lt;/strong&gt; are published on-chain. At its heart is a &lt;em&gt;two-curve&lt;/em&gt; SNARK architecture - a pairing-friendly curve (BLS12-377) for succinct proofs, and a companion curve (BW6-761) for efficient proof recursion and aggregation. Together these enable Aleo’s &lt;strong&gt;record model&lt;/strong&gt;, in which every piece of state (owner addresses, values, etc.) is kept private (ciphertext) unless explicitly marked public. In this article we dive into the mathematics of these curves, why they were chosen, and how Aleo uses them to encode and encrypt records. We give concrete Leo code examples for elliptic operations and record handling, and sketch diagrams of the curve relationships and record commitments to illuminate Aleo’s elegant design.&lt;/p&gt;
  &lt;h2 id=&quot;gjyQ&quot;&gt;The BLS12-377 Curve&lt;/h2&gt;
  &lt;p id=&quot;n6Dp&quot;&gt;Aleo’s primary elliptic curve is &lt;strong&gt;BLS12-377&lt;/strong&gt;, a Barreto–Lynn–Scott (BLS) curve at ~128-bit security. Formally, BLS12-377 is defined over the prime field $\mathbb{F}_q$ with characteristic&lt;/p&gt;
  &lt;section style=&quot;background-color:hsl(hsl(24,  24%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;VOJM&quot;&gt;q=258664426012969094010652733694893533536393512754914660539884262666720468348340822774968888139573360124440321458177&lt;/p&gt;
  &lt;/section&gt;
  &lt;p id=&quot;OKsu&quot;&gt;(approximately $2^{377}$) and its group order $r$ is a 253-bit prime&lt;/p&gt;
  &lt;section style=&quot;background-color:hsl(hsl(24,  24%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;dgfL&quot;&gt;r=8444461749428370424248824938781546531375899335154063827935233455917409239041.&lt;/p&gt;
  &lt;/section&gt;
  &lt;p id=&quot;fFCn&quot;&gt;The curve equation is of Weierstrass form (for example, $y^2 = x^3 + 4$) and admits an efficiently computable &lt;em&gt;bilinear pairing&lt;/em&gt; of embedding degree 12. In practice this means we have groups $G_1 = E(\mathbb{F}&lt;em&gt;q)[r]$ and $G_2 = E&amp;#x27;(\mathbb{F}&lt;/em&gt;{q^2})[r]$ such that a pairing e: G1×G2 → μr ⊂ Fq12 satisfies bilinearity $e(aP,bQ) = e(P,Q)^{ab}$, which is crucial for many SNARK verification equations. The cofactor of the curve is small (so the prime-order subgroup is large), and the parameters are chosen to resist known attacks (BLS12-377 has no small twists, etc.). Its security level (≈ 128 bits) is comparable to well-known curves like BLS12-381 or secp256k1, but it was specifically tuned for efficient SNARK proofs.&lt;/p&gt;
  &lt;p id=&quot;ZwTP&quot;&gt;A key property is that $r \approx 2^{253}$, meaning field operations in $\mathbb{F}_q$ are about 1.5× larger than the 254-bit Ethereum BN254 curve but slightly smaller than 381-bit. Arithmetic on BLS12-377 (e.g. point addition and scalar multiplication) is very fast in software, and numerous libraries and hardware circuits have been optimized for it. Importantly, its cofactor is 1, and it is defined with a simple $y^2=x^3+b$ form, making implementation and formal verification straightforward. In Aleo, BLS12-377 is used in SNARK generation and verification: the circuit proofs (Marlin/Varuna) are ultimately checked via group and field operations in this curve. As Aleo’s dev docs note, “BLS12-377 … supports fast proof verification” while an alternative twisted-Edwards “Edwards-BLS12” curve is used to “enable efficient circuit operations”.&lt;/p&gt;
  &lt;p id=&quot;aXSR&quot;&gt;Mathematically, one may write a BLS12-377 point addition or scalar multiplication in Leo as operations over two coordinates $(x,y)\in \mathbb{F}_q$. For example, if $G\in G_1$ is the generator, then $2G$ and $3G$ are computed in $\mathbb{F}_q$ by the usual elliptic curve formulas. (Leo code supports this via built-in group primitives.) We will show examples of doing such operations in Leo below.&lt;/p&gt;
  &lt;h2 id=&quot;JmVr&quot;&gt;Decaf377 (Twisted Edwards “Edwards BLS12”)&lt;/h2&gt;
  &lt;p id=&quot;uR16&quot;&gt;In addition to the short Weierstrass form of BLS12-377, Aleo also uses a &lt;em&gt;twisted Edwards&lt;/em&gt; variant on the same base-field modulus. This Edwards curve, colloquially called &lt;strong&gt;Decaf377&lt;/strong&gt;, has the same 377-bit prime field $\mathbb{F}_q$ but is represented with a curve equation of the form $ax^2 + y^2 = 1 + dx^2y^2$. (In practice Aleo’s library provides a group &lt;code&gt;Address&lt;/code&gt; over this curve.) The Edwards curve is actually defined over a 253-bit prime field equal to the order $r$ of BLS12-377, hence its base-field size is ~2^253 (253 bits) and its scalar order is 251 bits. In other words, Decaf377 is a safe prime-order twisted Edwards curve with field characteristic equal to $r$. This curve is much “smaller” (fewer bits) than BLS12-377’s base field, making its operations (point add, multiply, hash-to-curve) very efficient in SNARK circuits. For example, the compressed size of a point in this group is 32 bytes (vs 48 bytes for a G1 point on BLS12-377). Aleo uses this Edwards curve for most in-circuit cryptography – e.g. Pedersen hashes, commitments, Schnorr signatures, etc. – because twisted Edwards addition formulas have very low R1CS cost.&lt;/p&gt;
  &lt;p id=&quot;0eie&quot;&gt;In practice, Leo provides an &lt;code&gt;address&lt;/code&gt; type (which is actually a point in this Edwards curve’s group) and a &lt;code&gt;Group&lt;/code&gt; interface for arbitrary base-point operations. For instance, one might write in Leo:&lt;/p&gt;
  &lt;pre id=&quot;69Cj&quot;&gt;// Example: scalar multiply on Edwards curve
import leo::crypto::Group;

function example_ec() {
    let G = Group::generator::&amp;lt;BLS12_377&amp;gt;();           // generator point on Edwards curve
    let scalar = 123456u64;
    let P = G * scalar;                                // scalar multiplication
    let Q = P + G;                                     // point addition
    let (x,y) = Q.to_xy_coordinates();
    output x as Field;                                 // output x-coordinate
}&lt;/pre&gt;
  &lt;p id=&quot;DXQQ&quot;&gt;Here &lt;code&gt;Group::generator::&amp;lt;BLS12_377&amp;gt;()&lt;/code&gt; yields the basepoint on the Edwards form of BLS12-377, and arithmetic operations &lt;code&gt;*&lt;/code&gt; and &lt;code&gt;+&lt;/code&gt; are done inside the SNARK circuit. The operations occur in $\mathbb{F}_r$ (the 253-bit field), so they are relatively cheap. In contrast, doing these in the 377-bit field or an extension field (as required for pairings) would be more costly. The decaf377 group has cofactor 8 with a fast “cofactor-clearing” map, so it provides a prime-order subgroup for signatures and commitments.&lt;/p&gt;
  &lt;h2 id=&quot;9eQ6&quot;&gt;Why BLS12-377 (Performance, Security, Pairings)&lt;/h2&gt;
  &lt;p id=&quot;nHGB&quot;&gt;Why did Aleo choose BLS12-377 rather than other popular curves (like BLS12-381 or BN254)? Several factors influenced this:&lt;/p&gt;
  &lt;ul id=&quot;0OXb&quot;&gt;
    &lt;li id=&quot;ad52&quot;&gt;&lt;strong&gt;SNARK Efficiency.&lt;/strong&gt; BLS12-377 is optimized for small proofs and fast verification. Its relatively moderate base-field size (377 bits) yields smaller curve arithmetic than 381 bits, and it was specifically chosen in the Zexe framework. Empirically, proofs over BLS12-377 verify faster than equivalent ones over heavier curves, improving throughput. (By contrast, Ethereum’s BN254 is 254-bit but has known security weaknesses at that bitlength, while BLS12-381 is larger and offers slightly higher security than needed.)&lt;/li&gt;
    &lt;li id=&quot;Kwm2&quot;&gt;&lt;strong&gt;Security Margin.&lt;/strong&gt; The 377-bit prime and 253-bit order give a security level over 125 bits. This comfortably exceeds 128-bit symmetric security. In the long term, stronger curves might be added, but currently 125+ bits is ample. The chosen curve resists known attacks and was scrutinized in the literature.&lt;/li&gt;
    &lt;li id=&quot;zIL3&quot;&gt;&lt;strong&gt;Pairing Properties.&lt;/strong&gt; BLS12-377 has embedding degree 12, enabling efficient Type-3 pairings. This is important for the underlying Marlin/Varuna SNARK (which uses KZG polynomial commitments based on pairings) and any Groth16 proofs internally. BLS12-377 was paired with a compatible “two-cycle” partner (as we discuss below) to support recursion.&lt;/li&gt;
    &lt;li id=&quot;VNsS&quot;&gt;&lt;strong&gt;Library Support.&lt;/strong&gt; The Aleo/SnarkVM ecosystem had mature implementations for BLS12-377 (from Zexe and others). This gave confidence in correctness and performance. Moreover, the pairing-friendly curve allows reuse of many existing proof systems without reinventing them.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;N4Nf&quot;&gt;In summary, BLS12-377 strikes a balance: strong enough security, very efficient for both scalar-field arithmetic (for in-circuit use) and group operations (for proofs), and pairing-friendly for succinct SNARKs.&lt;/p&gt;
  &lt;h2 id=&quot;9AVh&quot;&gt;The BW6-761 Curve and 2-Chain Construction&lt;/h2&gt;
  &lt;p id=&quot;We3H&quot;&gt;Aleo’s SNARK architecture uses &lt;em&gt;two&lt;/em&gt; curves in tandem. The second is &lt;strong&gt;BW6-761&lt;/strong&gt;, a so-called Pinch–Cook curve specially constructed to pair with BLS12-377. BW6-761 has embedding degree 6 and is defined so that its &lt;em&gt;subgroup order&lt;/em&gt; equals the base-field characteristic of BLS12-377. In other words, if we write&lt;/p&gt;
  &lt;figure id=&quot;0DmI&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/13/06/13061952-8f6f-4ccc-b5b2-ec0cdf336470.png&quot; width=&quot;263&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;pCLw&quot;&gt;and let $r_{BW6}$ be the prime order of the BW6-761 group, then&lt;/p&gt;
  &lt;figure id=&quot;45K5&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/1b/d7/1bd745be-72e8-4a13-8d74-51cf32c8c549.png&quot; width=&quot;99&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;XBd0&quot;&gt;This “2-cycle” property (base field of one equals group order of the other) is crucial for recursive SNARKs.&lt;/p&gt;
  &lt;p id=&quot;Resc&quot;&gt;Concretely, BLS12-377’s base field $\mathbb{F}&lt;em&gt;q$ has size 377 bits; BW6-761’s scalar field is 761 bits (so its base field is larger, ~761 bits) and it has order $\approx 377$ bits. Thus, arithmetic in BW6’s base field implements 761-bit operations, about twice as heavy per multiplication as BLS12-377’s 377-bit field. However, this price is paid in order to do proof aggregation: Aleo uses BW6-761 to build a SNARK circuit that verifies&lt;/em&gt; proofs produced over BLS12-377. For example, to aggregate $k$ Groth16 proofs (each on BLS12-377), Aleo simply writes a BW6-761 circuit that takes those proofs as public inputs and verifies each one. Since in this circuit the BLS12-377 base field element (such as a target exponent in a pairing) is just an integer mod $r{BW6}$, it becomes native arithmetic in the BW6 SNARK. In effect, operations like exponentiation by $q$ (BLS12 base field) are just multiplications by $r_{BW6}$ (BW6’s group order), which are free (since $r_{BW6}\equiv 0$ in the exponent).&lt;/p&gt;
  &lt;p id=&quot;Abit&quot;&gt;The result is a &lt;em&gt;constant-size&lt;/em&gt; aggregated proof: Groth16 ensures each proof is 3 group elements, so naively $k$ proofs would give $O(k)$ size. But by verifying them inside one BW6 SNARK, Aleo obtains a single proof attesting to &lt;strong&gt;all&lt;/strong&gt; of them. As the Maya ZK blog explains, “the aggregation algorithm is simply the Groth16 SNARK over BW6-761 for a circuit verifying $k$ proofs”. In other words, Aleo’s proof-of-proof circuit uses Groth16 on BW6-761 with an R1CS encoding of the BLS12-377 verifier. The creative insight is that the enormous embedding (12 → 6) matches up so that the internal exponentiations are over integers mod $r_{BW6}$ (trivial) instead of mod $q^{something}$.&lt;/p&gt;
  &lt;p id=&quot;hpRV&quot;&gt;Practically, this “2-chain” approach yields constant-size proofs no matter how many are aggregated. (By contrast, SNARKPack or other inner-product schemes only reduce but still depend on $k$ logarithmically.) Aleo’s papers report that verifying &lt;em&gt;one&lt;/em&gt; BLS12-377 proof inside a BW6-761 SNARK costs on the order of $45,$K R1CS constraints. This is heavier than verifying a proof natively (Groth16 on BLS12-377 is quite fast), but it enables recursive proofs. Future plans (e.g. VeriexZexe) even replace the Groth16 recursion with PlonK, roughly halving the constraint count.&lt;/p&gt;
  &lt;p id=&quot;iiyE&quot;&gt;Mathematically, one can summarize the pairing cycles as follows: BLS12-377 has embedding degree 12, BW6-761 has degree 6, and they form a reciprocal 2-cycle. The BW6 base field $\mathbb{F}&lt;em&gt;{p&lt;/em&gt;{BW6}}$ is ~2× larger than BLS12’s (761-bit vs 377-bit), but its order equals BLS12’s field characteristic. In shorthand,&lt;/p&gt;
  &lt;figure id=&quot;dP5D&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/67/ea/67ea20e9-9368-45a3-b406-5d224447d49b.png&quot; width=&quot;339&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;4tzi&quot;&gt;Thus one can map a BLS12 pairing equation into BW6 arithmetic. Without this, recursively proving a pairing check in the same curve would cost millions of constraints, which is infeasible. The BW6-761 curve (sometimes called “Pinch–Cook curve”) was specially constructed for this use.&lt;/p&gt;
  &lt;h2 id=&quot;FjRB&quot;&gt;The Aleo Record Model&lt;/h2&gt;
  &lt;p id=&quot;2qFi&quot;&gt;Before diving deeper into pairings, let us step back to how Aleo uses elliptic curves for &lt;strong&gt;data records&lt;/strong&gt; and privacy. Aleo adopts a UTXO-like &lt;em&gt;record model&lt;/em&gt; (inspired by Zcash), where each application state change consumes old records and creates new ones. However, unlike typical UTXO, each record can contain arbitrary key-value data (not just a coin amount) and, crucially, every &lt;em&gt;private&lt;/em&gt; field is encrypted. By default, &lt;strong&gt;everything is private&lt;/strong&gt; unless marked public. The on-chain ledger never sees raw secret values – only ciphertexts and commitments.&lt;/p&gt;
  &lt;p id=&quot;ToH7&quot;&gt;Concretely, an Aleo program defines a &lt;code&gt;record&lt;/code&gt; type with named fields. For example:&lt;/p&gt;
  &lt;pre id=&quot;Pz78&quot;&gt;record AssetRecord {
    owner   as address.private;   // owner’s public address (encrypted)
    balance as u64.private;       // asset balance (encrypted)
}&lt;/pre&gt;
  &lt;p id=&quot;hMbw&quot;&gt;Here both &lt;code&gt;owner&lt;/code&gt; and &lt;code&gt;balance&lt;/code&gt; are &lt;code&gt;.private&lt;/code&gt;, indicating they must be encrypted before storing on-chain. The owner field is an Aleo account address (a public key), but as a record entry it is kept private to avoid linking. A record also carries a special &lt;code&gt;nonce&lt;/code&gt; (a group element) which ensures each record has a &lt;strong&gt;unique serial number&lt;/strong&gt;; this nonce is public in the record data (indeed, in the storage proof one sees the nonce). In summary, each stored record consists of:&lt;/p&gt;
  &lt;ul id=&quot;GLgn&quot;&gt;
    &lt;li id=&quot;XMH6&quot;&gt;&lt;strong&gt;apk&lt;/strong&gt; (address public key) – the owner of the record (encrypted on-chain, but used as a public commitment),&lt;/li&gt;
    &lt;li id=&quot;Ehle&quot;&gt;&lt;strong&gt;payload/data&lt;/strong&gt; – all the user-defined fields (encrypted or public as tagged),&lt;/li&gt;
    &lt;li id=&quot;peDZ&quot;&gt;&lt;strong&gt;serial number nonce&lt;/strong&gt; ($\rho$) – a group element unique to this record,&lt;/li&gt;
    &lt;li id=&quot;ogd7&quot;&gt;&lt;strong&gt;predicate data&lt;/strong&gt; ($\Phi_b,\Phi_d$) – flags or conditions on record birth/death (used for program logic).&lt;br /&gt; These components are hashed together into a &lt;strong&gt;record commitment&lt;/strong&gt; $cm$.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;sd43&quot;&gt;Records are tied to programs: only the program that created a record (identified by program ID) can later update or spend it. When a record is created in a transaction, the program includes its (encrypted) payload and assigns a new random nonce. Later, to spend that record, the owner must reveal its serial number $\rho$ (derived from the nonce) in a spending proof. Revealing the correct $\rho$ publicly prevents double-spending because the same nonce cannot be used twice. The nonce itself is computed via a pseudorandom function of the owner’s secret key and some index: in SnarkVM this is done as $\rho = \text{PRF}(ask, index)$ where $ask$ is the account’s spending key and &lt;code&gt;index&lt;/code&gt; is usually a counter or random salt. In code, one does not compute $\rho$ manually; it is automatically generated. But conceptually, each record’s serial number depends on the owner’s private key, binding the record to that key.&lt;/p&gt;
  &lt;p id=&quot;o5YN&quot;&gt;Importantly, &lt;strong&gt;only ciphertexts and commitments appear on-chain&lt;/strong&gt;. For a private entry like &lt;code&gt;balance&lt;/code&gt;, the program’s R1CS variables work with the plaintext integer internally, but the published record stores an encryption of it. Aleo provides an account &lt;em&gt;view key&lt;/em&gt; so that users can decrypt records they own. As the docs state: “Only the sender and receiver with their corresponding account view keys are able to decrypt the private entries”. The owner’s view key is derived from their secret key and published only to them (not to validators). This ensures the ledger is private by default: outsiders see only record commitments $cm$, not the actual addresses or values.&lt;/p&gt;
  &lt;p id=&quot;fV9o&quot;&gt;Another contrast is with traditional account-balance models: in Aleo, programs manage isolated records, not global storage slots. One can still have “mappings” or public state by using special public records, but that’s opt-in. By default, the record model gives concurrency (many records can be spent independently) and privacy.&lt;/p&gt;
  &lt;h2 id=&quot;I78z&quot;&gt;Record Encryption on BLS12-377&lt;/h2&gt;
  &lt;p id=&quot;gluE&quot;&gt;How is the &lt;em&gt;encryption&lt;/em&gt; of record fields actually done? In Aleo, encryption is based on elliptic-curve Diffie–Hellman over the Edwards BLS12-377 group. Specifically, when a program outputs a record with a private field $m$, the prover performs an &lt;strong&gt;ECDH&lt;/strong&gt; operation between the owner’s public address and a random scalar to derive a symmetric key. In SnarkVM’s Rust library, this is implemented in &lt;code&gt;Plaintext::encrypt(address, randomizer)&lt;/code&gt;. The steps are roughly (see code):&lt;/p&gt;
  &lt;ol id=&quot;C7U5&quot;&gt;
    &lt;li id=&quot;mR5E&quot;&gt;&lt;strong&gt;Pick a randomizer.&lt;/strong&gt; the prover samples a fresh scalar $r$ (called the &lt;em&gt;randomizer&lt;/em&gt;) for the record.&lt;/li&gt;
    &lt;li id=&quot;axyQ&quot;&gt;&lt;strong&gt;Compute shared key.&lt;/strong&gt; multiply the recipient’s address point $A$ by $r$ (point-scalar multiply). Take the &lt;em&gt;x-coordinate&lt;/em&gt; of $r \cdot A$ to form a field element $k = x(rA)$. This is the &lt;code&gt;plaintext_view_key&lt;/code&gt;.&lt;/li&gt;
    &lt;li id=&quot;X2z7&quot;&gt;&lt;strong&gt;Derive stream cipher.&lt;/strong&gt; hash $k$ (with domain separation) to generate one random field element per plaintext field bit.&lt;/li&gt;
    &lt;li id=&quot;sYY1&quot;&gt;&lt;strong&gt;Mask the plaintext.&lt;/strong&gt; add each hash output to the corresponding plaintext field. In effect $c_i = m_i + h_i(k)$ in $\mathbb{F}_r$.&lt;/li&gt;
  &lt;/ol&gt;
  &lt;p id=&quot;gDAT&quot;&gt;Concretely, if $A$ is the owner’s public key (a group element on the Edwards curve) and $r$ is a private scalar, then the shared secret is $[r]A$. The prover uses $\text{Key} = x([r]A)$ to derive a one-time pad. Only someone with the owner’s &lt;em&gt;view key&lt;/em&gt; (which equals the owner’s private address scalar $a$) can recompute $x([a] [r]G) = x([r]A)$ and thus the hash stream.&lt;/p&gt;
  &lt;p id=&quot;EFpF&quot;&gt;Mathematically, the encryption of a plaintext vector $m = (m_1,\dots,m_n)$ is:&lt;/p&gt;
  &lt;figure id=&quot;Oty6&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/d2/2b/d22b3907-e9a3-422f-ab49-f734a2aa1ad0.png&quot; width=&quot;308&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;WIoa&quot;&gt;where $H_i$ are hash-derived field elements. Since addition in $\mathbb{F}_r$ is invertible, knowing $k$ allows subtraction of $H_i(k)$ from the ciphertext $c_i$ to recover $m_i$. In code one never sees $c_i$ as a field; instead SnarkVM internally constructs a &lt;code&gt;Ciphertext&lt;/code&gt; object from the fields of $m$ and the derived randomizers.&lt;/p&gt;
  &lt;p id=&quot;Yj7N&quot;&gt;From the record-model perspective, this means: in a record structure, any &lt;code&gt;.private&lt;/code&gt; entry is encrypted to the owner’s address via ECDH. For example, in the earlier &lt;code&gt;AssetRecord&lt;/code&gt;, both &lt;code&gt;owner&lt;/code&gt; and &lt;code&gt;balance&lt;/code&gt; would be encrypted. The &lt;em&gt;nonce&lt;/em&gt; of the record (a group element) is public and is computed as $\rho = [ask],(H(index))$ where $ask$ is the owner’s spend key and $H(index)$ is a fixed group map – essentially another ECDH-like PRF. The resulting ciphertexts and nonce $\rho$ are then hashed into the record’s commitment.&lt;/p&gt;
  &lt;p id=&quot;wwN5&quot;&gt;We emphasize - &lt;em&gt;encryption happens off-chain as part of proof generation&lt;/em&gt;. A Leo programmer simply writes &lt;code&gt;owner as address.private&lt;/code&gt; and works with &lt;code&gt;owner&lt;/code&gt; as a public key type in the code; under the hood, after proof generation, the payload will be replaced by ciphertext. For example:&lt;/p&gt;
  &lt;pre id=&quot;LMwd&quot;&gt;record AssetRecord {
    owner   as address.private;   // owner (encrypted to this address)
    balance as u64.private;       // balance (encrypted)
}

function transfer(
    input rec as AssetRecord.record, 
    input to as address.private, 
    input amt as u64.private
) {
    // Subtract amt from sender’s balance
    sub rec.balance amt into new_balance;
    // Create sender’s new record with remaining balance
    cast rec.owner new_balance into rec1 as AssetRecord.record;
    // Create receiver’s new record with amt
    cast to 0u64 amt into rec2 as AssetRecord.record;
    // Output both as new records
    output rec1 as AssetRecord.record;
    output rec2 as AssetRecord.record;
}&lt;/pre&gt;
  &lt;p id=&quot;2Rbf&quot;&gt;In this example, &lt;code&gt;rec.owner&lt;/code&gt; and &lt;code&gt;rec.balance&lt;/code&gt; are handled as private values. The Leo compiler and runtime ensure they are encrypted to the appropriate addresses. (The second record’s &lt;code&gt;to&lt;/code&gt; field is also marked &lt;code&gt;.private&lt;/code&gt;, meaning the receiver address itself is stored privately.) Note that we never explicitly code the ECDH encryption – the SNARKVM library does it when emitting the transaction. We simply mark data as private in the type system. This ensures “all records in Aleo are fully private” as noted in Aleo docs.&lt;/p&gt;
  &lt;h2 id=&quot;1eCt&quot;&gt;Commitments and Record Serial Numbers&lt;/h2&gt;
  &lt;p id=&quot;NLEU&quot;&gt;While encryption hides contents, Aleo also uses cryptographic &lt;em&gt;commitments&lt;/em&gt; to bind data. Each record $r$ has a &lt;strong&gt;commitment&lt;/strong&gt; $cm = \mathsf{Commit}(apk, \text{data}, \Phi_b, \Phi_d, \rho)$, where $apk$ is the owner’s public key, “data” are the payload fields, $\Phi_b,\Phi_d$ are any birth/death predicates, and $\rho$ is the nonce/serial. This commitment (often a Pedersen hash on the Edwards group) is included in the proof, so that one proves “I know a record with values that hash to $cm$”. On-chain only $cm$ (and later the spent serial) are revealed. As Aleo documentation explains, commitments “ensure that sensitive information isn’t revealed but allow verification of correctness”.&lt;/p&gt;
  &lt;p id=&quot;jzBI&quot;&gt;The following diagram (adapted from Zexe) illustrates the commit step for a record:&lt;/p&gt;
  &lt;figure id=&quot;KbIM&quot; class=&quot;m_column&quot; data-caption-align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/e6/83/e6836796-f04c-4f37-9017-9be4a63be437.png&quot; width=&quot;1000&quot; /&gt;
    &lt;figcaption&gt;&lt;em&gt;Aleo record commitment. The “Commit” function hashes together the owner’s address public key (&lt;code&gt;apk&lt;/code&gt;), the encrypted data payload, the serial nonce $\rho$, and any program predicates. The result $cm$ is the record commitment stored on-chain.&lt;/em&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;jnPv&quot;&gt;In the figure above, the box “Commit” represents a Pedersen or cryptographic hash into a group. Notice the inputs: the owner’s public key (&lt;code&gt;address public key&lt;/code&gt;) and the &lt;code&gt;payload data&lt;/code&gt; are fixed by the program logic; the serial nonce $\rho$ is a fresh random group element (derived via the PRF on the owner’s key); and $\Phi_b,\Phi_d$ capture conditions like “this record was just created” or “this record is consumed”. The output is $cm$, published on-chain when the record is created. Later, when spending that record, one reveals $\rho$ publicly so anyone can check $\rho$ hasn’t been used before (preventing double-spend).&lt;/p&gt;
  &lt;p id=&quot;IvFe&quot;&gt;Taken together, Aleo’s private-record architecture works as follows: all critical data (&lt;code&gt;owner&lt;/code&gt;, &lt;code&gt;balance&lt;/code&gt;, etc.) are encrypted with ECDH on BLS12-377; a one-time nonce $\rho$ (also group-valued) is derived and used to tie the record to its owner; and the combination is hashed to a commitment $cm$. The SNARK proof for any transaction then shows that the new commitments and serials are correctly computed from the program’s logic and old commitments, without revealing plaintext data. This achieves confidentiality (hiding user data) and integrity (through hash commitments) simultaneously.&lt;/p&gt;
  &lt;h2 id=&quot;NVFW&quot;&gt;Pairing and SNARK Proof Structure&lt;/h2&gt;
  &lt;p id=&quot;rFyc&quot;&gt;Aleo’s proving system (based on SnarkVM and Marlin) uses &lt;strong&gt;pairing-based SNARKs&lt;/strong&gt;. Concretely, when you execute a program function on private inputs, SnarkVM compiles it to an R1CS and generates a proof (via a variant of Groth16 or PlonK). Verifying that proof involves pairings on BLS12-377. For example, in a Groth16-style proof one checks an equation of the form&lt;/p&gt;
  &lt;figure id=&quot;AahO&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/01/3d/013df9e1-337b-44d3-bff0-e56023b708ae.png&quot; width=&quot;290&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;h3F2&quot;&gt;for some group elements $A,B,C,D$ derived from the proof and public inputs. Each $e(\cdot,\cdot)$ is a BLS12-377 pairing. Thus Groth16 verification on BLS12-377 would require evaluating three pairings (and one exponentiation). Representing a pairing inside another SNARK directly is extremely expensive: one estimate is that a single BLS12-377 pairing costs on the order of $2^{24}$ R1CS gates. That is why Aleo employs the &lt;em&gt;two-curve&lt;/em&gt; trick described above.&lt;/p&gt;
  &lt;p id=&quot;pUwr&quot;&gt;In practice, Aleo does the following: user transactions are executed and proved off-chain. The resulting proofs (for each program transition) are posted on-chain in aggregated form. A validator node can verify each Groth16 proof by doing a few pairings in native code. But if one wants to build a recursively-verified chain of proofs (for, say, a super-light client), Aleo can prove the validity of proofs inside another SNARK using BW6-761. This way the verifier only sees one BW6-761 proof, and need not perform many BLS12-377 pairings itself.&lt;/p&gt;
  &lt;p id=&quot;jCrS&quot;&gt;To summarize:&lt;/p&gt;
  &lt;ul id=&quot;Mip7&quot;&gt;
    &lt;li id=&quot;Uv39&quot;&gt;&lt;strong&gt;Marlin/Varuna SNARKs on BLS12-377.&lt;/strong&gt; Aleo’s AVM compiles programs to R1CS over the BLS12-377 scalar field. A Marlin SNARK is generated (trusted-setup SRS given). This uses KZG polynomial commitments (which themselves rely on BLS12-377 pairings) to construct the proof.&lt;/li&gt;
    &lt;li id=&quot;aAhG&quot;&gt;&lt;strong&gt;Groth16 on BW6-761 for recursion.&lt;/strong&gt; If a user wants to aggregate proofs, Aleo can pack them into a single statement: “I know Groth16 witnesses that produce these $k$ proofs on BLS12-377”. This statement is checked by a BW6-761 Groth16 circuit (the witness includes the original Groth proofs). Thanks to $r_{BW6}=q_{BLS}$, the heavy exponentiations become simple multiplies, and the circuit size is modest (e.g. ≈45k constraints to verify one proof).&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;9Vp7&quot;&gt;The result is that Aleo proofs can be chained: a proof can certify not only a program execution but also the correctness of a previous proof (and so on). As one article explains, “a recursive SNARK allows us to generate a single proof for the claim ‘there exist valid proofs that prove the validity of $t_1$ and $t_2$’… compressing $p_1$ and $p_2$ into one proof”. In practice this means final blocks can attest to the validity of all history with constant overhead. (Of course, building the proving circuits and keys is heavier than a simple aggregator, but it is feasible with the tailored curves.)&lt;/p&gt;
  &lt;h2 id=&quot;nb4I&quot;&gt;Leo Code Examples&lt;/h2&gt;
  &lt;p id=&quot;r8wm&quot;&gt;We now give some concrete Leo snippets showing elliptic operations and record handling. Recall that in Leo, the &lt;code&gt;address&lt;/code&gt; type is a group element (Edwards-BLS12-377) and operations like &lt;code&gt;+&lt;/code&gt; and &lt;code&gt;*&lt;/code&gt; on group values correspond to elliptic curve addition and scalar multiplication. Likewise, marking a field as &lt;code&gt;.private&lt;/code&gt; causes the runtime to encrypt it.&lt;/p&gt;
  &lt;p id=&quot;65y6&quot;&gt;&lt;strong&gt;Example 1:&lt;/strong&gt; &lt;em&gt;Elliptic curve operations (Edwards BLS12-377).&lt;/em&gt; Suppose we want to do some low-level curve arithmetic. We might write:&lt;/p&gt;
  &lt;pre id=&quot;pIRy&quot;&gt;import leo::crypto::Group;

// Compute point P = 5*G and Q = G+P on the Edwards curve
function curve_ops_example:
    let G = Group::generator::&amp;lt;BLS12_377&amp;gt;();  // basepoint on Edwards curve
    let P = G * 5u64;                        // scalar multiply by 5
    let Q = G + P;                           // point addition
    let (x,y) = Q.to_xy_coordinates();       // get affine coordinates
    output x as Field;&lt;/pre&gt;
  &lt;p id=&quot;Pozt&quot;&gt;This function multiplies the generator by 5 and adds it, demonstrating group ops. Internally this uses the Edwards curve formulas over the 253-bit field. Because Edwards operations are efficient, such code is relatively cheap in an R1CS. One could similarly implement a Pedersen hash by repeatedly adding fixed base points (not shown here), or verify a simple Schnorr signature by doing $R + [s]G = [e]A$ check – all on this group.&lt;/p&gt;
  &lt;p id=&quot;ylu5&quot;&gt;&lt;strong&gt;Example 2:&lt;/strong&gt; &lt;em&gt;Record splitting/transfer.&lt;/em&gt; Consider a token balance record as above:&lt;/p&gt;
  &lt;pre id=&quot;SLdC&quot;&gt;record Asset {
    owner   as address.private;
    balance as u64.private;
}

function transfer_asset:
    input rec as Asset.record;
    input recipient as address.private;
    input amount as u64.private;
    // Compute new sender balance
    sub rec.balance amount into new_bal;
    // Create output record for sender with reduced balance
    cast rec.owner new_bal into out1 as Asset.record;
    // Create output record for recipient with received amount
    cast recipient 0u64 amount into out2 as Asset.record;
    output out1 as Asset.record;
    output out2 as Asset.record;&lt;/pre&gt;
  &lt;p id=&quot;DGF9&quot;&gt;Here &lt;code&gt;transfer_asset&lt;/code&gt; takes a private &lt;code&gt;Asset&lt;/code&gt; record &lt;code&gt;rec&lt;/code&gt; and a private &lt;code&gt;amount&lt;/code&gt; to send. It computes &lt;code&gt;new_bal = rec.balance - amount&lt;/code&gt;. Then it uses &lt;code&gt;cast ... into ... as Asset.record&lt;/code&gt; to form new records: one for the sender (&lt;code&gt;out1&lt;/code&gt;) and one for the recipient (&lt;code&gt;out2&lt;/code&gt;). The &lt;code&gt;recipient&lt;/code&gt; address is provided as an input and marked &lt;code&gt;.private&lt;/code&gt; so that the new record’s owner field is encrypted to that address. Finally the two records are &lt;code&gt;output&lt;/code&gt; from the function. When this transaction executes, the resulting records will be encrypted such that only the owner can decrypt them.&lt;/p&gt;
  &lt;p id=&quot;hk6E&quot;&gt;Notice that in Leo code, we never see the encryption directly. We simply mark data as &lt;code&gt;.private&lt;/code&gt;. The documentation clarifies that “an entry which has a visibility of &lt;code&gt;private&lt;/code&gt; is encrypted and stored on the ledger. This enables users to securely and privately transfer record data”. After proof generation, SnarkVM applies the group-based encryption described above to each private field.&lt;/p&gt;
  &lt;p id=&quot;DlSc&quot;&gt;These examples illustrate how elliptic curve primitives are integrated into Aleo programs: group operations appear as normal arithmetic in Leo, and record handling (cast/output) triggers encryption under the hood. Together with pairing-based SNARK verification, this makes Aleo a fully end-to-end zero-knowledge execution environment.&lt;/p&gt;
  &lt;h2 id=&quot;jnRi&quot;&gt;Aleo Mainnet and Future Directions&lt;/h2&gt;
  &lt;p id=&quot;SUwS&quot;&gt;Aleo’s mainnet (launched in sep 2024) is one of the first to offer &lt;strong&gt;privacy by default&lt;/strong&gt; for general computation. It supports features like private assets, private DeFi, and private voting. On-chain, validators see only commitments and ciphertexts; users decrypt with their view keys. Aleo also allows optional public state (mappings) when needed.&lt;/p&gt;
  &lt;p id=&quot;xgOn&quot;&gt;Key capabilities today include:&lt;/p&gt;
  &lt;ul id=&quot;f5zS&quot;&gt;
    &lt;li id=&quot;7ghj&quot;&gt;&lt;strong&gt;SNARK-based execution.&lt;/strong&gt; All Aleo program transitions are proved with zk-SNARKs. As the docs explain, this is achieved via Marlin/Varuna proofs and a universal SRS.&lt;/li&gt;
    &lt;li id=&quot;YeCe&quot;&gt;&lt;strong&gt;Record commitments and serials.&lt;/strong&gt; Every new record produces a unique commitment and serial number, enabling private UTXO-style accounting with double-spend protection.&lt;/li&gt;
    &lt;li id=&quot;PXd7&quot;&gt;&lt;strong&gt;Account model interoperability.&lt;/strong&gt; Although primarily UTXO-like, Aleo also has account-level view keys and addresses for convenience.&lt;/li&gt;
    &lt;li id=&quot;Qm6R&quot;&gt;&lt;strong&gt;Built-in curves and algorithms.&lt;/strong&gt; The platform natively supports BLS12-377 and BW6-761; developers do not need to implement pairing themselves.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;x0LG&quot;&gt;Looking forward, Aleo continues to innovate on both curves and protocols. Recent research (e.g. the “Veri-Zexe” paper) replaces the Groth16 recursion with PlonK, yielding about &lt;strong&gt;10× faster proving&lt;/strong&gt; and halving verification cost. Longer-term, one may imagine multi-curve hierarchies or use of PLONKish SNARKs to further shrink proofs. Aleo’s two-curve design is already a powerful enabler: in principle, it lets the protocol recursively attest to an unbounded chain of proofs (each new proof certifying all prior ones). This property could be leveraged for ultra-light clients or verifiable blockchains like Mina.&lt;/p&gt;
  &lt;p id=&quot;4fZ4&quot;&gt;Moreover, the separation of private vs. public state is a distinctive feature: applications can choose to reveal data when needed (e.g. in a poker game revealing flop cards) without sacrificing general privacy. As zero-knowledge technology advances, Aleo may integrate new curve families or hashing schemes, but the core strategy of efficient curves + record encryption will remain. For now, Aleo’s mainnet showcases the practical viability of pairing-friendly curves and record-centric ZK, and points toward a future where cryptographers can design complex privacy protocols with confidence in their underlying curves.&lt;/p&gt;
  &lt;p id=&quot;3CD4&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;ak4E&quot;&gt;&lt;strong&gt;Write by alexanderblv for the Aleo, May 2025 &lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;KI1X&quot;&gt;&lt;strong&gt;&lt;a href=&quot;https://x.com/alexander_blv&quot; target=&quot;_blank&quot;&gt;x.com/alexander_blv&lt;/a&gt; &lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;ZD3E&quot;&gt;&lt;strong&gt;ERC20 - 0x1e1Aa06ff5DC84482be94a216483f946D0bC67e7&lt;/strong&gt;&lt;/p&gt;

</content></entry></feed>