LURK: Lambda, the Ultimate Recursive Knowledge (Experience Report)

We introduce Lurk, a new LISP-based programming language for zk-SNARKs. Traditional approaches to programming over zero-knowledge proofs require compiling the desired computation into a flat circuit, imposing serious constraints on the size and complexity of computations that can be achieved in practice. Lurk programs are instead provided as data to the universal Lurk interpreter circuit, allowing the resulting language to be Turing-complete without compromising the size of the resulting proof artifacts. Our work describes the design and theory behind Lurk, along with detailing how its implementation of content addressing can be used to sidestep many of the usual concerns of programming zero-knowledge proofs.


INTRODUCTION
Assume that you query a remote database with key , and want a guarantee that the remote database server is answering the query correctly; or that you need to prove that you have a sufficiently high balance in your financial account in order to participate in a given transaction , but don't want to reveal how much money you have. These are examples of "proofs" that a (potentially secret) value and a public input satisfy a given relationship, and play an important role in many secure applications where computations are performed by untrusted servers, including blockchain systems.
Succinct Non-Interactive Arguments of Knowledge (or SNARKs) [Ben-Sasson et al. 2014b] have been a very exciting area of research in the last decade: a SNARK allows one party (the prover) to prove to another party (the verifier) that a certain computation has been performed correctly. Specifically, it allows the verifier to prove the existence of a witness such that = ( , ) for a publicly known input . It is easy to see that the above examples can be cast in this framework.
One such proof is the witness itself, with the verification procedure being to simply recompute the function . The crucial property of SNARKs, however, is that they produce a proof which is shorter than and can be verified faster than recomputing (particularly, sublinear in either). An additional important property of SNARKs is that they can be zero-knowledge (zk-SNARKs), revealing no information about to the verifier. This paper introduces Lurk 1 , a new LISP-based programming language which automatically constructs zk-SNARKs for arbitrary programs, avoiding ad-hoc compilation of programs into flat circuits -a process which imposes serious constraints on the size and complexity of computations that can be achieved in practice. Although zk-SNARKs theoretically enable applications like those described above, the possibility of deploying them has so far been impeded by the lack of a practical and general language stack. One author conceived of Lurk after his experience implementing the Filecoin proofs [Fisch et al. 2018] 2 , which consist largely of Merkle-inclusion proofs at scale.
Lurk emerged through a design effort to generalize such proofs of knowledge, to exploit recent cryptographic proving-system breakthroughs, and to solve software-engineering usability problems still unaddressed by new cryptography. Claims about computation provable in arithmetic circuits (the implementation language of SNARK statements) generally end with the observation that such circuits are Turing-complete. This theoretical equivalence might lead prospective proof implementers to wrongly believe that proofs of execution of programs written in conventional programming languages can be easily represented in SNARK circuits, but this is not the case. In fact, non-trivial programs expressed in R1CS require that control structures be unrolled and recursive programs be translated into a witnessed form which is unintuitive to audit or author by hand and penalizes performance of general-purpose programs.
Lurk solves this problem by integrating a concise interpreter with its cryptographic backend, to express proofs over the evaluation of a high-level Turing-complete source programming language. In other words, the Lurk interpreter sequentially reduces Lurk programs until a terminal result remains, with no intermediate representation required: the (content-addressed) human-readable program is the input to the arithmetic circuit proving its reduction; and the final result of evaluation is similarly legible.
This approach emerged following the observation that Merkle proofs are isomorphic to functional membership checks when data structures are represented as Merkle DAGs. In this model, hashconsed pointers to atomic values, including symbols, allocate compound data on a virtual heap to instantiate a RAM without a linear address space. Expressions encoded in such a content-addressed data language transparently (without need of a compilation step) specify arbitrary computation using the evaluation model outlined in McCarthy's original Lisp paper [McCarthy 1960]. This requires only a handful of primitive operations, which suffice to resolve the expressiveness problems of flat circuits. The resulting language is a human-readable and writable Lisp dialect with code-data equivalence. It can be directly proved with a single universal SNARK circuit iterated by a suitable recursive proving system (e.g. Nova) 2 CRYPTOGRAPHIC BACKGROUND

Arithmetization
The first step in the construction of a SNARK is to arithmetize the computation , which for the purpose of this paper, can be thought as expressing a computation into a format that makes easier to prove its correctness. Following the work on Quadratic Span Programs (QSP) [Gennaro et al. 2013], a very popular arithmetization for SNARKs is Rank 1 Constrained Systems (R1CS) which are a universal model that can encode any computation .
Mapping a computation to an R1CS system of constraints can be a tedious effort, but it is also one of the main computational bottlenecks in SNARKs, requiring large overhead for the prover in terms of both computation time and memory. Indeed a circuit verifying the computation of must basically "write down" the entire computation trace as a witness to prove its correctness.

Incrementally Verifiable Computation
Starting with the work of Valiant [2008], researchers have been studying alternative ways to construct SNARKs that would not require construction of a circuit for the entire computation . One approach involves verifying that each step of the computation has been performed correctly and then use recursion to fold the correctness proofs of the first − 1 steps and the correctness proof of the ℎ step into a proof of correctness of all steps.
The reason this is appealing is that it allows to verify a computation as it is executed: in this case the cryptographic verification engine is only applied to the transition function of the machine executing a program.
This gives rise to the notion of Incrementally Verifiable Computation (IVC) where the function is executed as the repeated composition of a smaller function that at the ℎ step takes as input the result of the previous step (the input and output of the computation is the state of the machine over which the big computation is run).
The most efficient candidate for IVC is currently Nova ]. This scheme arithmetizes the circuit of the small function as an R1CS and then it shows how to recursively prove that = ( , ) as the composition of ℓ iterations of the transition function .

Cryptographic Commitments
A cryptographic commitment is a protocol that can be thought of as the equivalent of an opaque envelope. A sender who has a value , produces a commitment = ( ) to , and later can open the commitment to . A commitment must be binding, i.e. can only be opened to a unique value ; it is usually compressing in the sense that | | < | | and can be hiding, i.e. the value reveals no information about . In practice commitments are built from collision-resistant hash functions = ( , ) for some randomness . The collision-resistant property guarantees binding, the range of is usually smaller than its domain, and under some reasonable assumptions the randomness protects the secrecy of .
Commitments are a crucial tool in SNARKs. For example, note that the efficiency requirement on the SNARK verifier prevents them from even reading a description of the function . One way to deal with this is to assume some preprocessing phase where the function is committed to a short string that can be handled by the verifier (preprocessing SNARKs). In IVC schemes like Nova, the intermediate computation steps are compressed in order for the state of the recursion not to grow too much.
We will show later how the design of Lurk allows for a natural expression of commitments to both values and functions.

Some Terminology
The construction of a SNARK is usually divided into two parts [Benarroch et al. 2019]. A cryptographic backend which given a suitable arithmetization of the function builds a cryptographic proof of the correctness of the computation (the QSP work and its improvement by Groth [2016] are examples of cryptographic backends for R1CS).
A SNARK frontend on the other hand is a way to map a program written on some high-level programming language to a good arithmetization that can then be fed to the correct cryptographic backend. Lurk is a frontend that pairs programs written in a dialect of LISP to R1CS circuits that allow building a meaningful proof about these programs. In the next section we describe the drawbacks of a naive compilation of code into arithmetic circuits, and show how Lurk avoids these pitfalls.

The drawbacks of direct compilation
The overarching goal of Lurk is to express the user's computation in the form of an R1CS instance -or its close predecessor an arithmetic circuit -that can be used by a cryptographic proof backend. At first glance, one may be tempted to think that a stepwise translation of the instructions of the high-level program, coupled with the definition of an ad-hoc set of combinators [Hughes 1982;Wand 1982b] would suffice. But this direct compilation approach has compounding drawbacks.
First, as mentioned in Section 1, R1CS is a flat structure, which admits no explicit control, akin to SMT formulas. Directly compiling into this language requires flattening the structure of the program, notably unrolling all loops and branches, and inlining functions -transformations that are a staple of the domain-specific languages (DSLs) implementing a direct compilation approach [Bellés-Muñoz et al. 2022;Chin et al. 2021;Eberhardt and Tai 2018;Ozdemir et al. 2022]. As these transformations lengthen the size of the R1CS form of the program, they have a negative impact on the proving time.
Second, as most SNARK prover constructions require space linear in the size of the computationnot to mention that many use space-time trade-offs that worsen this space complexity [Thaler 2013] -, this expansion of the program statement risks making prover memory a practical bottleneck.
Since it only considers a fragment of the program at any given proving step, the approach of IVC bypasses these limitations, albeit at the expense of requiring the source program to reflect the transition function of an abstract state machine. Unfortunately, not all programs natively present such an incremental nature, which is why Lurk eschews the direct compilation approach.

A generalized approach to design proof languages with IVC
On the other hand, an important and often used method of defining a programming language is to give an interpreter for it. In many cases, this interpreter can be defined by way of an abstract machine, either by design, or using a set of elementary techniques to translate a wide range of formal semantics into the desired form [Danvy 2008].
Once equipped with this definitional abstract machine, we can use it to recover a compiler to the instruction set of our domain-specific "virtual machine" -our cryptographic proving backend. We can model those instructions as combinators, whose names and arguments are defined in our source language, and where their generated low-level code is the arithmetic circuits to which they immediately translate, also known as "gadgets" in the cryptographic literature. The cryptographic proving protocol is the "runtime" operating on our circuit. This combinator-based approach to deriving a compiler from an interpreter was pioneered by Wand [1982aWand [ ,b, 1983 and later formalized Draft, 2023 by Ager et al. [2003]. Lurk adopts one instance of this general blueprint to achieve the general architecture described by Fig. 1.
initial public Lurk program and arguments 1 , . . . , states of the Lurk abstract machine 0 . . . witnesses provided by the prover public output U Lurk abstract machine (as a circuit) Π proof artifact finally produced by Nova The overall approach of Lurk is hence to solve the problem of a difficult compilation task by abstracting it: instead of compiling a potentially large program ⟨ ⟩ expressed in a language directly to R1CS, we write a small-step abstract machine interpreter U for , and derive a step-wise compiler to circuits from that interpreter.
This specific use of a zero-knowledge proof system involves changing the nature of the proof statement, informally from ∃ : = ( , ), where is expressed as a circuit, to ∃ : = U (⟨ ⟩ , , ), where ⟨ ⟩ is expressed as a representation of a program in . Yet, in a pure model of computation, where the deterministic evaluation of on its input data ( , ) is definitionally equivalent to the iteration of our interpreter on (⟨ ⟩ , , ), we would claim this is a better approach: we expect that the user will represent their intent more directly using a programming languagebased description, steeped in omnipresent assumptions about basic control flow.

The Lurk interpreter
Lurk is an eagerly evaluated, purely functional programming language. Its syntax and semantics are inspired by Lisp and Scheme, and consist of a superset of the lambda calculus with let and letrec, along with lists and a handful of data types.
We chose to adopt eager evaluation and a small-step abstract machine, by using a variant of the CEK machine [Felleisen et al. 2009;Felleisen and Friedman 1986]. The choice of a small-step semantics [Plotkin 2004], beyond leveraging the simplicity of the nominal CEK machine, avoids duplicating rules and premises that a big-step semantics [Kahn 1987] may require to handle exceptions and divergence, and thus makes for a smaller circuit.
Let's now give some intuition of the reduction: the transition rules of the CEK machine are all syntax-directed, and hence mutually exclusive if we pattern-match them accurately. For instance, let's consider the variable rule, in the sub-case where the sought variable isn't at the head of the environment. In a textbook elaboration of the CEK machine to a Lisp-like language with lists, with , ranging over symbols, over environments, over continuations, and over values, it would look like: ⟨ , ( , ) :: , ⟩ −→ ⟨ , , ⟩ As in all reduction rules, the pattern on the left of the arrow is related by the reduction rule to the product on the right. The circuit expression of this single relation consists, at a high level, in: (1) assembling a conjunction of boolean clauses that check the conformance of the input term to the pattern, (2) constructing the production from the input pattern elements, and forming a clause expressing its equality with the output of the step, Draft, 2023 (3) relating the two items above through an implies gadget, which links the pattern clause to the product clause in the usual (boolean) sense. In our example, we would check that the control word is indeed a symbol, that it is a valid variable name (rather than e.g. a reserved word), that the environment is non-empty, and that the first binding in the environment is not to the sought-after symbol. We would then form the output state using the same control word and continuation, but with a smaller environment corresponding to the tail of the initial one.
For the purpose of expressing this, the circuit uses an array of high-level tools: • convenience gadgets for circuit arithmetic, • gadgets which encode boolean arithmetic using arithmetic circuits (e.g. and( , ) can be encoded as × , not( ) as 1 − , etc), • a fast hash function with field elements as a codomain, which allows fast equality comparisons, • further, if the current level of elaboration of the program is not sufficient to pattern-match a case, we can ask the prover to unfold more sub-terms of the program through hidden witnesses. To elaborate on the last point, we note the full computation is not part of the input of any specific step of proof. Rather, Lurk defines hash-consing along the structure of each term of the language [Goto 1974], and pervasively uses domain-separated hash pointers for redexes of the source language. We will detail this in section 4.3. Those hashes, performed with a cryptographically-secure and field-algebra-friendly hash function [Grassi et al. 2021], embody the notion of a cryptographic commitment, so that a proof starts with just a public commitment to the whole program, and the prover progressively produces the required fragments of the computation as witnesses during the proof creation process 3 .
In the case of Lurk, the technique of hash-consing carries recursively through the whole structure of the program: when unfolding a top-level if construct, the prover only needs to reveal the condition of the if, and a commitment to each of its two subsequent branches, in order to pursue the proof to the next frame. One useful side-effect of this discipline is that the proving process will stop early if the evaluation of the program itself stops early through branching, incurring no penalty from the size of the unused branches.
We can think of the overall circuit, then, as being a disjunction of the individual "pattern-match implies product" clauses elaborated through the process above. The actual circuit implementation differs slightly from this discipline, but only because it aims at achieving maximal sharing between sub-clauses used in several rules, as a performance optimization. The primitive data types, supported by Lurk (e.g. 64-bit unsigned integers and their operations) also generate a special set of constraints (e.g. bound checks), defined as a specific gadget, and that we do not elaborate on here.
Finally, since the application of the Lurk circuit provides proofs of individual evaluation steps, it is the responsibility of a cryptographic backend to link them together in a coherent proof relating the whole sequence of statements: besides the recursive Nova proof system , Lurk is general enough to also support an aggregative backend called Snarkpack [Gailly et al. 2022], used in legacy applications.

A BRIEF OVERVIEW OF LURK
In this section, we describe the surface language and novel features of Lurk, along with providing examples motivating those features' utility.

Lurk is a Lisp
Inheriting from typical Lisp tradition, Lurk has no syntax of note. Everything is an expression. Some expressions are self-evaluating; for example, the number 3 evaluates to itself. This can be seen when entered into the Lurk REPL: Note that Lurk explicitly tracks the count of "iterations" (in this case, 1), representing the "cost" or number of "clock cycles" needed to evaluate the expression to normal form.
Lurk data is content-addressed, which means that every expression is identified and may be referred to by a type-tagged, cryptographic hash digest.

Looping and branching
Lurk supports evaluating only some branches of a program, as well as unbounded loops and recursion. This is a strict improvement on the direct compilation approach, which represents branching as the full evaluation of all sub-clauses of a disjunction, and requires unrolling loops at compilation time. For instance, the number of iterations of a sieve of Erathostenes is only equal to the worst case when the number is prime.

Commitments
Lurk has built-in support for cryptographic commitments. Importantly, this opening only works if the value had indeed been previously committed, thus registering the commitment with the Lurk interpreter. Because Lurk commitments are based on cryptographically-secure hashes -just as all compound data in Lurk is [Grassi et al. 2021] -it is computationally intractable to discover a second preimage to the digest represented by a commitment. For this reason, a commitment can be viewed as an index into a write-once store, such that all uses of the same commitment represent the same underlying value. This property is known as computational binding.
Lurk also allows open to operate on field elements, omitting the (comm . . . ) wrapper. For brevity, we will henceforth use bare field elements to refer to commitments.
Lurk also supports explicit hiding commitments with a salt. When hiding is unimportant, commit creates commitments with a default secret of 0. However, any field element can be used as the secret, which makes Lurk commitments hiding as well as binding. For reproducibility, all commitments in the remaining examples of this report were created with a secret of 0. In real applications, secret salts should be selected at random if data hiding is required.

Functional Commitments
Because Lurk allows commitments to any Lurk expression, we can also commit to functions. Functional commitments, introduced by Libert et al. [2016] and extended to function-hiding commitments by Boneh et al. [2021], enable a prover to commit to a secret function and later prove that = ( ) for public and without revealing any other information about . In Lurk, this notion enjoys native support in the language.

Draft, 2023
Iterations : 2 Result : ( comm 0 x1aec2d8c ) The above is a commitment to a function that squares its input, then adds seven. Prior work on efficient function-hiding commitments [Boneh et al. 2021] could only posit an R1CS description of a function , and modified commitments built from the indexed relations used in holographic proof systems ] to convey both a commitment to the defining binary relation of , and a proof that is total and univalent. By simply choosing the functional Lurk language itself as a basis of how to describe functions, instead of the relational R1CS, Lurk can represent function-hiding commitments more directly. Lurk's deterministic semantics, which extend the lambda-calculus, offer a straightforward argument for the universality and well-formedness of function definitions. We can thus construct and evaluate an expression that can only be proven to evaluate to one value: the result of applying the function to a given input. More formally, in the above, we rely on our built-in structural, nominal hashing scheme ← Commit(⟨ ⟩ , ) using a secret to be binding and hiding on the Lurk description ⟨ ⟩ of a function and our deterministic evaluator U (·, ·) to build a proof that we know a witness for the following relation: where L is the set of definable Lurk functions, and = 0x1aec2d8c, = 9, = 88 are public. 4 Interestingly, even though the set of primitive operations Lurk supports is quite small, they enable the possibility of higher-order functional commitments for free.

Credit-Score
This example was first introduced by Boneh et al. [2021]. Consider a credit bureau, who wants to preserve the secrecy of its proprietary rating algorithm while also proving that it applies its algorithm fairly to all parties. This can be accomplished by first committing to a function that implements the secret algorithm, then opening function applications on each individual's credit data. By only interacting with the function through its commitment, it can be verified that the same function is used in all cases, while only revealing the result.
We demonstrate with a simple (if unrealistic) map-reduce based algorithm (note that the definition of map-reduce itself is elided): ( letrec (( plus ( lambda ( a b ) (+ a b ))) ( square ( lambda ( x ) (* x x ))) ( secret-function ( lambda ( credit-data ) ( map-reduce square plus 0 credit-data )))) ( commit secret-function )) Next, consider the complementary situation, in which the algorithm is public, but consumers' individual credit data is secret. In this case, an individual commits to their private data wrapped in a functional interface (as in the data-interface example above).

Zero-Knowledge Type-Certificates (zk-TCs)
Another interesting program to implement in Lurk is a type checker. Modern languages and proof assistants often feature extremely rich type systems, with computationally expensive type-checking algorithms. Next, consider that well-typed program fragments are frequently written once and shared as a library with many users, each of whom must independently re-check the program in order to verify its correctness.
With a Lurk typechecker, however, we can generate zero-knowledge proofs that a specific program corresponds to a given type. For example, consider a hypothetical Lurk function type-check which takes a program and a type as inputs (in some well-typed language) and returns a boolean if the typing is correct. A zero-knowledge proof that the above program returns T proves that my-program inhabits my-type. A user of my-program can now cheaply verify this type-signature, without having to recompute the type-checking operation. We call this kind of proof a zero-knowledge type-certificate or zero-knowledge proof of type-correctness (zk-TC).
The Yatima Compiler 5 implements a Lurk backend for the Lean Theorem Prover and Programming Language [de Moura and Ullrich 2021]. Yatima also includes a self-hosted kernel (or trusted typechecker) of Lean written in itself, extended with content-addressing using Lurk expressions and the Poseidon hash function [Grassi et al. 2021]. Yatima then compiles this content-addressing kernel to Lurk, as well as its input declarations (Lean expressions and types).
An example of a formal proof in Lean is addComm, which inductively proves commutativity of addition over Nat, the type of natural numbers (defined using the Peano construction). This proof relies on other Lean declarations as dependencies, such as Eq.symm, Nat.zeroAdd, etc., each of which has their own definition (and may contain further dependencies). Each of these declarations is content-addressed in Lurk as a functional commitment, as described in the previous section, as is the Yatima kernel itself. The Yatima kernel is constructed to allow Lurk proofs that dependencies are type-correct, and therefore can perform IVC across any Lean dependency graph.
A zk-TC not only enables faster verification of types, but also verification of type-signatures where the type, program, or even typechecker itself are private inputs. In other words, one can use a zk-TC to prove that one knows of a program that inhabits a certain type, without revealing the program. For example, suppose one had a proof of Fermat's Last Theorem in Lean, but did not want to reveal it: With the Yatima compiler one can generate a Lurk zk-TC with secretProof as a private input, which is a zero-knowledge proof that one possesses a valid formal proof of the above theorem (revealing only its Poseidon hash). This succinct zk-TC of secretProof could even, with an appropriate visual encoding, fit within the margins of a book of Diophantine equations.

DISCUSSION
So far, we have described the insights that led to the development of Lurk, and demonstrated that its architecture is adequate to serve real-world programming use cases. In this section, we compare our project to related work, and explore upcoming work.

Related work
The insight that one could bypass both the challenge of a compilation to R1CS and that of segmenting proofs expressed by very large generated circuits by representing the cryptographic interface of a SNARK as iterations of a specialized virtual machine is not new, and predates the existence of performant cryptography to implement it. For instance, landmark approaches have approached simulating a simple CPU, attesting to the validity of memory accesses and intermediate state representations [Ben-Sasson et al. 2014a,b]. The area has gained renewed interest of late [Bruestle et al. 2023].
Moreover, SNARKs have enjoyed an affinity with programmable blockchains, which accumulate updates to a shared state through the execution of programs expressed in a high-level DSL, also called smart contracts. There, zero-knowledge proofs tackle a scalability problem: the most frequent approach for validators of a blockchain to verify the correctness of state updates is to re-execute each of those and come to agreement on their outcome through a Byzantine fault-tolerant consensus protocol. The computation hence needs repeating roughly as many times as there are validators, which is wasteful. Hence streams of work both academic and industrial have sought to model state update messages as proofs, offering succinct verification of the outcome of state updates, rather than an explicit one (see [Bonneau et al. 2021;Bowe et al. 2020;Gluchowski 2021;Polygon 2022;Starkware 2021;Zhang 2019]).
However, those zk-VMs assume that computation is segmented ex ante, as successive executions of reasonably-sized smart contract invocations. The design of prover machines for public proving platforms must hence tailor the hardware to the largest possible contract's execution, and bound it explicitly through gating at the protocol level.
To our knowledge, Lurk is the only work that places the iterative nature of computation at the level of the evaluation of a programming language, picking a "Goldilocks" level of granularity between microprocessor emulators (which risk being sent bookkeeping instructions of little relevance in the high-level proof), and blockchain zk-VMs (which express but the incremental nature of a sequence of smart contract updates).

Future Work
Formal verification. By implementing Lurk as an interpreter, we are reducing the surface area of the complex R1CS conversion step to that of using a simpler and universal reduction circuit, so that the resulting proof process can have simpler semantics with fewer enginering sharp edges (see [Aumasson 2022] for a tour of historic pitfalls in this area).
This approach translates changes the slope of the verification challenge: while some direct compilation DSLs have made notable progress on applying formal methods to the verification of an Draft, 2023 R1CS compiler [Chin et al. 2021], verifying Lurk consists more simply in verifying the correctness of a specific circuit. As we have a formal semantics for this circuit, in the form of a CEK machine, this area is ripe for formal verification.
Backend extensions. At its core Lurk is an interpreter based on the small-step CEK abstract machine. While this offers the advantage of simplicity, the requirement of the cryptographic interface is more loosely defined as an abstract machine with deterministic transitions. This leaves open the exploration of a big-step abstract machine [Danvy 2008] reducing the number of evaluation steps, or that of other abstract machines, such as the CESK machine should we want to extend the language with e.g. control effects [Felleisen et al. 2009].
On the cryptographic side, Lurk uses the Nova proof backend to generate proofs of its execution trace, which forces the proof process to pay an identical overhead on each step of the proof. The recent SuperNova , allows using alternate circuits for each step, while only paying for the cost of the specific circuit invoked by each particular step. This would allow us to precede the reduction operated by our interpreter by optimization steps applicable to our domain (e.g. constant folding, see [Appel 1991]). In effect, this would let us build an optimizing compiler modularly, using the staple of compiler phases.
Proof-Carrying Data. A more involved direction in which to extend Lurk's use of IVC is research towards support for Proof-Carrying Data [Chiesa and Tromer 2010]. PCD is a powerful cryptographic primitive describing computation occurring on the nodes as a directed acyclic graph (DAG) of messages, of which IVC embodies the special case of a path on the graph [Bünz et al. 2020]. Equipped with a cryptographic backend supporting PCD, Lurk would be able to model concurrent programming use cases involving mutually distrustful execution nodes, and build native support for it in the language.