An Intro into SNARKs and Trusted Computing
A summary of SNARKs and Trusted Computing from learning during my Summer 2026 Research Experience.
Overview:
Imagine that you are a weak client that needs to run a heavy piece of code. The best option that you have is to outsource that computation to another party (like Amazon Web Services). Now obviously you should be able to trust this party, but is there a way to ensure that the party is returning both a good result and that they actually ran the program that you sent? This is what a SNARK sets out to accomplish.
Informally, a SNARK (succinct non-interactive argument of knowledge) is a witness that allows a verifier (the client) to verify that their code was run and has a correct output by the prover (the server) sublinear with respect to the program work. Thus, when given a output and SNARK from a third party, the verifier can accept or reject the output based on the SNARK.
A trivial solution to verification would just be to run the code on your own and verify that they gave the correct solution, but this isn't interesting in our use case, thus we constrain SNARKs to be sublinear with respect to the work of the computation.
Thus SNARKs are:
- Succinct: Allow for fast verification (sublinear w.r.t. program runtime/work)
- Non-interactive: Only one message to transfer between prover and verifier
- Give an Argument of Knowledge: Ensures that the prover has executed this program (with non-negl prob)
Formal Definition:
Given a fast but potentially malicious prover , a slow but trusted verifier ,
a program , and input to the program , gives the
computation to to compute and receives output along with a proof
that convinces that (with
non-negligibleA function is non-negligible if s.t. for all . probability).
Specifically, a SNARK satisfies the following:
1.) Completeness: a honest can convince .
2.) Soundness: a dishonest cannot convince
with non-neglA function is non-negligible if s.t. for all . probability.
3.) Succinctness: 's cost to verify is sublinear to the work required to compute
This is a nice idea and all, but how do SNARKs really work? They seem useful for verifiers, but if they take too long to construct by the prover or have huge proof sizes, then they may not be practical. It turns out that there is a large amount of research into making SNARKs quick for the prover to construct, verifier to verify, and small enough to be useful. Thus lots of tools have been released to work with SNARKs by researchers.
For now we will just go over the GKR protocol (which is only one approach of many to construct SNARKs). The protocol can be divided into 3 main steps:
1.) Arithmetization into Layered Circuits
2.) Sumcheck Protocol
3.) Polynomial Commitment
Circuit Arithmetization:
The first step of the GKR protocol is to arithmetize the program into a layered circuit. Essentially, it turns out that most programs can be represented by simple arithmetic constraints. To formalize this, let a constraint set be a set of rules applied to values of a set of variables. Also, arithmetic constraints only addition, multiplication, and equality are supported over a finite field modulo .
So when given a program , the compiler produces constraints such that . We call these values, witnesses.
For instance, we may represent a program like Y = (X_0 + X_1) * (X_0 * X_1) as:
Note that loops, conditional branching, and assertions can also be represented this way! For example an if statement that may look something like:
if (a == b):
c = 1;
else:
c = 2;
This turns into the following arithmatic constraints:
Where is set by the prover to 1 or 0 depending on which branch is taken. Assertions and looping can be derived in a similar manner and are left as an exercise to the reader.
Memory accesses are somewhat tougher to handle, but treating them as a type of consistency check it is easier to represent. In general, we just extract all memory operations and check that for every LOAD from an address, that the value from the last address that was accessed is the same. Encoding a memory instruction as 4 elements of an Address, Value, Load/Store bit, and Timestamp gives us all the information in order to perform a consistency check in linear time using some permutation checking algorithm.In particular, let and note that for arrays and of length that is a valid permutation of iff . So for a randomly generated and two arrays and , it can be shown that they are permutations with high probability iff . This can be accomplished with multiplications.
Now that we have a correct and sound way to arithmetize our program we need to make it cleaner in order to progress on the succinct requirement. This is accomplished with a layered circuit. There exists ways to make a "good" circuit that minimizes the depth by expanding the width with more gates.
For instance has a depth of 5, but can be lessened to 4 by using an extra constraint that the prover must add (which can continue to happen up to get logarithmic depth):
Now that we have minimal depth circuits, we use the sumcheck protocol to allow for quick verification of the layered circuit.
Sumcheck Protocol:
In order to check that the circuit is correct checks inputs, outputs (that should match the claim provided by the prover), and the transitions between each layer. The sumcheck protocol is utilized to speed up the transition check of each layer of the circuit.
To check that an arbitrary transition with width , input nodes , and output nodes holds we have:
In order to check that , we can use sumcheckGoal: Verify . express as polynomial on bits of and instead now verify . This takes rounds to verify by having the prover provide claims for rounds . For each claim, the verifier checks , samples random , and computes quickly. Then at , finally checks to go through this in verifier time.
Ok great! This looks like a fast and efficient way to verify that an arithmatic layered circuit is correct if we use this approach times on each layer. However, this is still not succinct as the verifier (while only having to do layers, each verified in time) still has to evaluate each layer's input + witness where the size of the witness size of execution. Thus we need polynomial commitment to finally reach succinctness.
Polynomial Commitment:
Currently, we have an approach that turns correct arithmatic layered circuits into succinct checks and polynomial evaluation. However, opening a single point on layer is just as expensive as executing the program. Thus our final step is using a polynomial commitment scheme.
In short, our goal is to verify that for some (prover given) polynomial s.t. for , the time it takes to verify is sublinear to . So the process is have the prover commit to and send a short commitment to the verifier that is then used to compute by running GKR with prover and send it back to the prover. After this the prover computes and produces a short proof corresponding to . Then the verifier can verify this with the commitment quickly and accept if it passes.
One great PCS-adjacent method that accomplishes the same goals is a Merkle Tree. Specifically, this is called a vector commitment scheme but is very similar to a PCS. A Merkle Tree is defined as follows:
Let be a collision-resistant hash function for some . Then our goal is that given an array and arbitrary index , retrieve time. We then define for each pair of adjacent array entries as an gate s.t. . Then each hash produced by each gate is paired with the adjacent hash on its layer until there is only one hash at the top (resulting in a logarithmic depth hash tree). In order to commit, the prover sends to the verifier and the verifier gives back an arbitrary to find .
As for the prover, they need to help the verifier produce the path up the tree to re-derive starting from . Thus the prover provides entries to have the verifier hash back up the tree and derive .
Thus, while the prover has to deal with hashes (the commitment cost) and values to provide entries to the verifier given an arbitrary , the verifier only has a commitment size of , proof size of entries, and a final verification size of hashes. Thus we have achieved true succinctness for our SNARK generation/verification protocol!
Summary:
In short, the GKR protocol takes in a program and produces an arithmatic layered circuit with minimal depth, verifies the circuit using the sumcheck protocol to produce a fast polynomial evaluation of the circuit, and then finally uses a polynomial (or vector) commitment scheme to avoid the verifier having to open the entire program circuit by itself, producing a small proof (the SNARK) to go along with the output of the program.
Looking to the Future:
SNARKs are just a small tip of what cryptographic proof systems have to offer. There is lots of research to extend of concepts like SNARKs
and the field is still seeing lots of work done (with cryptocurrencies, trusted computing, and other topics).
Here are some papers and projects that I have found fairly interesting regarding SNARK research:
zk-SNARKs are a form of SNARK that also ensure a property of zero-knowledge. So a can convince
that it has successfully executed without giving away any knowledge of secret inputs to the computation.
zk-STARKs perform a similar task of a zk-SNARK but are also safe against
post-quantum adversaries.
Understanding zk-SNARKs - A paper summarizing/reviewing how SNARKs have been integrated into real world systems.
Jolt - A cool SNARK generation framework (zkVM) that is easy to set up and start generating proofs using constraints based around assembly instructions.
CoBBl Dynamic Constraint Generation - I worked on this project during Summer 2026 developing a Rust frontend for CoBBl! CoBBl breaks a program into basic
blocks and performs optimizations on them in order to generate constraints between blocks and states of the program (which turns out to be very fast and decreases prover times across an array of benchmarks).
Acknowledgements:
A great deal of the explanations and understanding that I have with this topic comes from a lecture from Benny for the class Introduction to Computer Security (15/18-330). Much of the examples in this post come from his slides :)