Arith-Circ is an embedded domain-specific language for building arithmetic circuits, a low-level universal model of computation. Given such a circuit, its execution on some given inputs can be verified very efficiently (in particular, much more efficiently than simply re-executing the circuit) with a cryptographic proof system such as Groth16.
circ :: ArithCirc Fr
circ = execCircBuilder $ do
i0 <- var <$> input
i1 <- var <$> input
compile2Out (mul i0 i1)import Arithmetic (ArithCirc)
import Expr (execCircBuilder)
import Lang (mul, var, input, compile2Out)
import Qap (Qap, arithCirc2Qap, Family, assignment, witness)In this simple example, we'll use the above Arith-Circ types and functions. We'll also use an IntMap for specifying inputs Fr of the BN254 elliptic curve.
import Data.IntMap.Strict qualified as M
import ZK.Algebra.Pure.Instances.BN254 (Fr)The simplest possible circuit consists of a single multiplication gate, corresponding to the product
circ :: ArithCirc Fr
circ = execCircBuilder $ do
i0 <- var <$> input
i1 <- var <$> input
compile2Out (mul i0 i1)Given inputs
trace :: Family Fr
trace = assignment circ ins
where
ins = M.fromList $ zip [0..] [6, 8]To prove that trace is indeed a valid execution trace, we'll need the circuit to be represented in a form preferred by the backend proof system, namely as a quadratic arithmetic program (or more generally, a rank-1 constraint system (R1CS)).
qap :: Qap Fr
qap = arithCirc2Qap circThen the claim that trace is a valid execution trace amounts to the existence of a certain polynomial
witnessPoly :: IO ()
witnessPoly =
case witness qap trace of
Nothing -> putStrLn "invalid trace"
Just w -> print wSee also Adjoint's arithmetic circuits.