├── README.lhs ├── test ├── Main.hs └── Test │ ├── Circuit │ ├── Affine.hs │ ├── Expr.hs │ └── Arithmetic.hs │ └── QAP.hs ├── .assets ├── adjoint.png ├── circuit.png └── arithmetic-circuit-example.svg ├── .gitignore ├── ChangeLog.md ├── src ├── Circuit.hs ├── Fresh.hs └── Circuit │ ├── Lang.hs │ ├── Dot.hs │ ├── Affine.hs │ └── Arithmetic.hs ├── stack.yaml ├── bench ├── Main.hs └── Circuit.hs ├── .hlint.yaml ├── .circleci └── config.yml ├── stylish-haskell.yaml ├── Example.hs ├── LICENSE ├── .github └── workflows │ ├── hlint.yml │ ├── cabal.yml │ └── stack.yml ├── default.nix ├── tex ├── 2f2322dff5bde89c37bcae4116fe20a8.svg ├── 9b325b9e31e85137d1de765f43c0f8bc.svg ├── 89f2e0d2d24bcf44db73aab8fc03252c.svg ├── d5c18a8ca1894fd3a7d25f242cbe8890.svg ├── 2ad9d098b937e46f9f58968551adac57.svg ├── 55a049b8f161ae7cfeb0197d75aff967.svg ├── 2d4c6ac334688c42fb4089749e372345.svg ├── 580e7a6446bf50562e34247c545883a2.svg ├── 2441df23627a504b2a4c6f5006893fd6.svg ├── 5e27bca98285ab8eccf4d53506baeaec.svg ├── a420c52aa24a502d60aef830b3b45f9f.svg ├── 9000cff3d46536c190fabb076ebe7cbb.svg ├── 7592e8ca3cc64009a29ef0fb58f65c76.svg ├── 52be0087c9da1f0683ccc50761e8bcab.svg ├── b2584d6517f9c72bcd800016d8d1fa0d.svg ├── 083da1124b81d709f20f2575ae9138c3.svg ├── d1dd493c98f06e9ef29b5fdc411e29f8.svg ├── bf01d477a12bededfd9d65617fedacc9.svg ├── cc073b29543f2694def1c15e2d40cb07.svg ├── 26affbf877c9c00bcf2d4f78f88cedbf.svg ├── dc35769e37858254d0d77fab2d83bcf4.svg ├── 2c4cf6568afbabb029a579215dfa6e3e.svg ├── 6e1ad13b9c0521871bb453942700c519.svg ├── e5079841837a9e98336245a5ecdb2538.svg └── 4fc182d9ca10181f85444d64991b4f62.svg ├── package.yaml ├── stack.yaml.lock └── arithmetic-circuits.cabal /README.lhs: -------------------------------------------------------------------------------- 1 | README.tex.md -------------------------------------------------------------------------------- /test/Main.hs: -------------------------------------------------------------------------------- 1 | {-# OPTIONS_GHC -F -pgmF tasty-discover -optF --tree-display #-} 2 | -------------------------------------------------------------------------------- /.assets/adjoint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdiehl/arithmetic-circuits/HEAD/.assets/adjoint.png -------------------------------------------------------------------------------- /.assets/circuit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdiehl/arithmetic-circuits/HEAD/.assets/circuit.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .stack-work/ 2 | *.sw[pon] 3 | *.prof 4 | *.prof.html 5 | *~ 6 | *.hi 7 | *.o 8 | .ghc.environment.* 9 | dist*/ 10 | TAGS 11 | dist-newstyle 12 | -------------------------------------------------------------------------------- /ChangeLog.md: -------------------------------------------------------------------------------- 1 | # Change log for arithmetic-circuits 2 | 3 | ## 0.2.0 4 | 5 | * Added JSON serialisation for arithmetic circuits. 6 | 7 | ## 0.1.0 8 | 9 | * Initial release. 10 | -------------------------------------------------------------------------------- /src/Circuit.hs: -------------------------------------------------------------------------------- 1 | module Circuit 2 | ( module Circuit.Arithmetic, 3 | module Circuit.Lang, 4 | module Circuit.Expr, 5 | ) 6 | where 7 | 8 | import Circuit.Arithmetic 9 | import Circuit.Expr 10 | import Circuit.Lang 11 | -------------------------------------------------------------------------------- /stack.yaml: -------------------------------------------------------------------------------- 1 | resolver: lts-14.7 2 | extra-deps: 3 | - elliptic-curve-0.3.0 4 | - galois-field-1.0.2 5 | - poly-0.4.0.0 6 | - semirings-0.5.3 7 | - pairing-1.0.0 8 | - arithmoi-0.8.0.0 9 | - bulletproofs-1.1.0 10 | - galois-fft-0.1.0 11 | - vector-algorithms-0.8.0.3 12 | - bitvec-1.0.3.0 13 | - mod-0.1.1.0 14 | - QuickCheck-2.13.2 15 | -------------------------------------------------------------------------------- /bench/Main.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE NoImplicitPrelude #-} 2 | 3 | -- To get the benchmarking data, run "stack bench". 4 | 5 | module Main where 6 | 7 | import Protolude 8 | 9 | import Criterion.Main 10 | 11 | import qualified Circuit 12 | 13 | main :: IO () 14 | main = defaultMain 15 | [ bgroup "Circuit to QAP translation" Circuit.benchmarks 16 | ] 17 | -------------------------------------------------------------------------------- /src/Fresh.hs: -------------------------------------------------------------------------------- 1 | module Fresh 2 | ( fresh 3 | , evalFresh 4 | , Fresh 5 | , FreshT 6 | ) where 7 | 8 | import Protolude 9 | 10 | type FreshT m a = StateT Int m a 11 | type Fresh a = FreshT Identity a 12 | 13 | evalFresh :: Fresh a -> a 14 | evalFresh act = runIdentity $ evalStateT act 0 15 | 16 | fresh :: Fresh Int 17 | fresh = do 18 | v <- get 19 | modify (+ 1) 20 | pure v 21 | -------------------------------------------------------------------------------- /.hlint.yaml: -------------------------------------------------------------------------------- 1 | - ignore: {name: Avoid lambda} 2 | - ignore: {name: Eta reduce} 3 | - ignore: {name: Redundant lambda} 4 | - ignore: {name: Collapse lambdas} 5 | - ignore: {name: Use String} 6 | - ignore: {name: Reduce duplication} 7 | - ignore: {name: Redundant bracket} 8 | - ignore: {name: Redundant if} 9 | - ignore: {name: Use list comprehension} 10 | - ignore: {name: Use section} 11 | - ignore: {name: Use &&} 12 | - ignore: {name: Replace case with fromMaybe} 13 | - ignore: {name: "Use :"} 14 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | docker: 5 | - image: fpco/stack-build:lts 6 | steps: 7 | - checkout 8 | - restore_cache: 9 | name: Restore Cached Dependencies 10 | keys: 11 | - arithmetic-circuits-{{ checksum "package.yaml" }} 12 | - run: 13 | name: Resolve/Update Dependencies 14 | command: stack setup 15 | - run: 16 | name: Run tests 17 | command: stack test --fast 18 | - save_cache: 19 | name: Cache Dependencies 20 | key: arithmetic-circuits-{{ checksum "package.yaml" }} 21 | paths: 22 | - ".stack-work" -------------------------------------------------------------------------------- /stylish-haskell.yaml: -------------------------------------------------------------------------------- 1 | language_extensions: 2 | - LambdaCase 3 | - RecordWildCards 4 | - OverloadedStrings 5 | - NoImplicitPrelude 6 | - FlexibleInstances 7 | - FlexibleContexts 8 | - ScopedTypeVariables 9 | - RankNTypes 10 | - ConstraintKinds 11 | - DataKinds 12 | - DeriveGeneric 13 | - GeneralizedNewtypeDeriving 14 | - MultiParamTypeClasses 15 | - OverloadedLists 16 | - PatternSynonyms 17 | - TypeFamilyDependencies 18 | newline: native 19 | steps: 20 | - simple_align: 21 | cases: false 22 | top_level_patterns: false 23 | records: false 24 | - imports: 25 | long_list_align: inline 26 | pad_module_names: true 27 | list_padding: 2 28 | empty_list_align: inherit 29 | align: group 30 | space_surround: false 31 | list_align: after_alias 32 | separate_lists: false 33 | - language_pragmas: 34 | style: compact 35 | remove_redundant: true 36 | align: true 37 | columns: 80 38 | -------------------------------------------------------------------------------- /Example.hs: -------------------------------------------------------------------------------- 1 | import Circuit.Arithmetic 2 | import Circuit.Expr 3 | import Circuit.Lang 4 | import qualified Data.Map as Map 5 | import Data.Pairing.BN254 (Fr, getRootOfUnity) 6 | import Fresh (evalFresh, fresh) 7 | import Protolude 8 | import QAP 9 | 10 | program :: ArithCircuit Fr 11 | program = 12 | execCircuitBuilder 13 | ( do 14 | i0 <- fmap deref input 15 | i1 <- fmap deref input 16 | i2 <- fmap deref input 17 | let r0 = mul i0 i1 18 | r1 = mul r0 (add i0 i2) 19 | ret r1 20 | ) 21 | 22 | roots :: [[Fr]] 23 | roots = evalFresh (generateRoots (fmap (fromIntegral . (+ 1)) fresh) program) 24 | 25 | qap :: QAP Fr 26 | qap = arithCircuitToQAPFFT getRootOfUnity roots program 27 | 28 | inputs :: Map.Map Int Fr 29 | inputs = Map.fromList [(0, 7), (1, 5), (2, 4)] 30 | 31 | assignment :: QapSet Fr 32 | assignment = generateAssignment program inputs 33 | 34 | main :: IO () 35 | main = 36 | if verifyAssignment qap assignment 37 | then putText "Valid assignment" 38 | else putText "Invalid assignment" 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Adjoint 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/hlint.yml: -------------------------------------------------------------------------------- 1 | name: HLint CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | jobs: 10 | build: 11 | name: hlint 12 | runs-on: ubuntu-16.04 13 | strategy: 14 | matrix: 15 | ghc: ["8.8.1"] 16 | cabal: ["3.0"] 17 | 18 | steps: 19 | - uses: actions/checkout@v1 20 | - uses: actions/setup-haskell@v1 21 | name: Setup Haskell 22 | with: 23 | ghc-version: ${{ matrix.ghc }} 24 | cabal-version: ${{ matrix.cabal }} 25 | 26 | - uses: actions/cache@v1 27 | name: Cache ~/.cabal/packages 28 | with: 29 | path: ~/.cabal/packages 30 | key: cabal-packages 31 | 32 | - uses: actions/cache@v1 33 | name: Cache ~/.cabal/store 34 | with: 35 | path: ~/.cabal/store 36 | key: cabal-store 37 | 38 | - uses: actions/cache@v1 39 | name: Cache dist-newstyle 40 | with: 41 | path: dist-newstyle 42 | key: dist-newstyle 43 | 44 | - name: Install hlint 45 | run: | 46 | cabal update 47 | cabal new-install hlint --installdir=dist-newstyle 48 | - name: Run hlint 49 | run: | 50 | dist-newstyle/hlint -g -e=hs 51 | -------------------------------------------------------------------------------- /.github/workflows/cabal.yml: -------------------------------------------------------------------------------- 1 | name: Cabal CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | jobs: 10 | build: 11 | name: cabal ${{ matrix.ghc }} 12 | runs-on: ubuntu-16.04 13 | strategy: 14 | matrix: 15 | ghc: ["8.8.1", "8.6.5", "8.6.4", "8.6.3", "8.6.2"] 16 | cabal: ["3.0"] 17 | 18 | steps: 19 | - uses: actions/checkout@v1 20 | - uses: actions/setup-haskell@v1 21 | name: Setup Haskell 22 | with: 23 | ghc-version: ${{ matrix.ghc }} 24 | cabal-version: ${{ matrix.cabal }} 25 | 26 | - uses: actions/cache@v1 27 | name: Cache ~/.cabal/packages 28 | with: 29 | path: ~/.cabal/packages 30 | key: cabal-packages-${{ matrix.ghc }} 31 | 32 | - uses: actions/cache@v1 33 | name: Cache ~/.cabal/store 34 | with: 35 | path: ~/.cabal/store 36 | key: cabal-store-${{ matrix.ghc }} 37 | 38 | - uses: actions/cache@v1 39 | name: Cache dist-newstyle 40 | with: 41 | path: dist-newstyle 42 | key: dist-newstyle-${{ matrix.ghc }} 43 | 44 | - name: Install dependencies 45 | run: | 46 | cabal update 47 | - name: Build 48 | run: | 49 | cabal new-build 50 | -------------------------------------------------------------------------------- /bench/Circuit.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE NoImplicitPrelude #-} 2 | 3 | module Circuit (benchmarks) where 4 | 5 | import Protolude 6 | 7 | import Circuit.Affine 8 | import Circuit.Arithmetic 9 | import Criterion.Main 10 | import Data.Curve.Weierstrass.BN254 (Fr) 11 | import qualified Data.Map as Map 12 | import Data.Pairing.BN254 (getRootOfUnity) 13 | import Fresh 14 | import QAP 15 | 16 | 17 | program :: ArithCircuit Fr 18 | program = ArithCircuit 19 | [ Mul (Var (InputWire 0)) (Var (InputWire 1)) (IntermediateWire 0) 20 | , Mul (Var (IntermediateWire 0))(Add (Var (InputWire 0)) (Var (InputWire 2))) (OutputWire 0) 21 | ] 22 | 23 | input :: Map.Map Int Fr 24 | input = Map.fromList [(0, 7), (1, 5), (2, 4)] 25 | 26 | benchmarks :: [Benchmark] 27 | benchmarks 28 | = [ bench "evaluating circuit" 29 | $ whnf (evalArithCircuit lookupAtWire updateAtWire program) (initialQapSet input) 30 | , bench "creating QAP (no interpolation)" 31 | $ nf (\c -> arithCircuitToGenQAP (evalFresh $ generateRoots (fromIntegral <$> fresh) c) c) program 32 | , bench "creating QAP (fast interpolation)" 33 | $ nf (\c -> arithCircuitToQAPFFT getRootOfUnity (evalFresh $ generateRoots (fromIntegral <$> fresh) c) c) program 34 | , bench "creating QAP (slow interpolation)" 35 | $ nf (\c -> arithCircuitToQAP (evalFresh $ generateRoots (fromIntegral <$> fresh) c) c) program 36 | ] 37 | 38 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | { mkDerivation, aeson, base, bulletproofs, containers, criterion 2 | , elliptic-curve, filepath, galois-fft, galois-field, hpack 3 | , markdown-unlit, MonadRandom, pairing, poly, process-extras 4 | , protolude, QuickCheck, quickcheck-instances, semirings, stdenv 5 | , tasty, tasty-discover, tasty-hunit, tasty-quickcheck, text 6 | , vector, wl-pprint-text 7 | }: 8 | mkDerivation { 9 | pname = "arithmetic-circuits"; 10 | version = "0.2.0"; 11 | src = ./arithmetic-circuits; 12 | libraryHaskellDepends = [ 13 | aeson base bulletproofs containers elliptic-curve filepath 14 | galois-fft galois-field MonadRandom poly process-extras protolude 15 | semirings text vector wl-pprint-text 16 | ]; 17 | libraryToolDepends = [ hpack ]; 18 | testHaskellDepends = [ 19 | aeson base bulletproofs containers elliptic-curve filepath 20 | galois-fft galois-field markdown-unlit MonadRandom pairing poly 21 | process-extras protolude QuickCheck quickcheck-instances semirings 22 | tasty tasty-discover tasty-hunit tasty-quickcheck text vector 23 | wl-pprint-text 24 | ]; 25 | testToolDepends = [ markdown-unlit tasty-discover ]; 26 | benchmarkHaskellDepends = [ 27 | aeson base bulletproofs containers criterion elliptic-curve 28 | filepath galois-fft galois-field MonadRandom pairing poly 29 | process-extras protolude semirings text vector wl-pprint-text 30 | ]; 31 | prePatch = "hpack"; 32 | homepage = "https://github.com/adjoint-io/arithmetic-circuits#readme"; 33 | description = "Arithmetic circuits for zkSNARKs"; 34 | license = stdenv.lib.licenses.mit; 35 | } 36 | -------------------------------------------------------------------------------- /.github/workflows/stack.yml: -------------------------------------------------------------------------------- 1 | name: Stack CI 2 | 3 | on: 4 | push: 5 | pull_request: 6 | schedule: 7 | - cron: "0 0 * * 1" 8 | 9 | jobs: 10 | build: 11 | name: stack ${{ matrix.plan.resolver }} 12 | strategy: 13 | matrix: 14 | os: [ubuntu-latest, macOS-latest] 15 | plan: 16 | - { build: stack, resolver: "lts-13" } # redundant because lts-14 checks ghc-8.6 already 17 | - { build: stack, resolver: "lts-14" } # ghc-8.6.5 18 | #- { build: stack, resolver: "nightly" } 19 | include: [] 20 | exclude: 21 | - os: macOS-latest 22 | plan: 23 | build: cabal 24 | 25 | runs-on: ${{ matrix.os }} 26 | steps: 27 | - name: Install OS Packages 28 | uses: mstksg/get-package@v1 29 | with: 30 | apt-get: ${{ matrix.apt-get }} 31 | brew: ${{ matrix.brew }} 32 | - uses: actions/checkout@v1 33 | 34 | - name: Setup stack 35 | uses: mstksg/setup-stack@v1 36 | 37 | - uses: actions/cache@v1 38 | name: Cache .stack-work 39 | with: 40 | path: .stack-work 41 | key: stack-work 42 | 43 | - uses: actions/cache@v1 44 | name: Cache ~/.stack 45 | with: 46 | path: ~/.stack 47 | key: stack 48 | 49 | - name: Install dependencies 50 | run: | 51 | set -ex 52 | stack --no-terminal --install-ghc --resolver $ARGS test --bench --only-dependencies 53 | set +ex 54 | env: 55 | ARGS: ${{ matrix.plan.resolver }} 56 | BUILD: ${{ matrix.plan.build }} 57 | 58 | - name: Build 59 | run: | 60 | set -ex 61 | stack --no-terminal --resolver $ARGS test --bench --no-run-benchmarks --haddock --no-haddock-deps 62 | set +ex 63 | env: 64 | ARGS: ${{ matrix.plan.resolver }} 65 | BUILD: ${{ matrix.plan.build }} 66 | -------------------------------------------------------------------------------- /src/Circuit/Lang.hs: -------------------------------------------------------------------------------- 1 | -- | Surface language 2 | module Circuit.Lang 3 | ( c 4 | , add 5 | , sub 6 | , mul 7 | , and_ 8 | , or_ 9 | , xor_ 10 | , not_ 11 | , eq 12 | , deref 13 | , e 14 | , cond 15 | , ret 16 | , input 17 | ) where 18 | 19 | import Protolude 20 | 21 | import Circuit.Affine (AffineCircuit(..)) 22 | import Circuit.Arithmetic (Gate(..), Wire(..)) 23 | import Circuit.Expr 24 | 25 | -- | Convert constant to expression 26 | c :: f -> Expr Wire f f 27 | c = EConst 28 | 29 | -- | Binary arithmetic operations on expressions 30 | add, sub, mul :: Expr Wire f f -> Expr Wire f f -> Expr Wire f f 31 | add = EBinOp BAdd 32 | sub = EBinOp BSub 33 | mul = EBinOp BMul 34 | 35 | -- | Binary logic operations on expressions 36 | -- Have to use underscore or similar to avoid shadowing @and@ and @or@ 37 | -- from Prelude/Protolude. 38 | and_, or_, xor_ :: Expr Wire f Bool -> Expr Wire f Bool -> Expr Wire f Bool 39 | and_ = EBinOp BAnd 40 | or_ = EBinOp BOr 41 | xor_ = EBinOp BXor 42 | 43 | -- | Negate expression 44 | not_ :: Expr Wire f Bool -> Expr Wire f Bool 45 | not_ = EUnOp UNot 46 | 47 | -- | Compare two expressions 48 | eq :: Expr Wire f f -> Expr Wire f f -> Expr Wire f Bool 49 | eq = EEq 50 | 51 | -- | Convert wire to expression 52 | deref :: Wire -> Expr Wire f f 53 | deref = EVar 54 | 55 | -- | Return compilation of expression into an intermediate wire 56 | e :: Num f => Expr Wire f f -> ExprM f Wire 57 | e = compileWithWire imm 58 | 59 | -- | Conditional statement on expressions 60 | cond :: Expr Wire f Bool -> Expr Wire f ty -> Expr Wire f ty -> Expr Wire f ty 61 | cond = EIf 62 | 63 | -- | Return compilation of expression into an output wire 64 | ret :: Num f => Expr Wire f f -> ExprM f Wire 65 | ret = compileWithWire freshOutput 66 | 67 | compileWithWire :: Num f => ExprM f Wire -> Expr Wire f f -> ExprM f Wire 68 | compileWithWire freshWire expr = do 69 | compileOut <- compile expr 70 | case compileOut of 71 | Left wire -> pure wire 72 | Right circ -> do 73 | wire <- freshWire 74 | emit $ Mul (ConstGate 1) circ wire 75 | pure wire 76 | 77 | input :: ExprM f Wire 78 | input = freshInput 79 | -------------------------------------------------------------------------------- /test/Test/Circuit/Affine.hs: -------------------------------------------------------------------------------- 1 | module Test.Circuit.Affine where 2 | 3 | import Circuit.Affine 4 | import qualified Data.Map as Map 5 | import Protolude 6 | import Test.Tasty.QuickCheck 7 | 8 | ------------------------------------------------------------------------------- 9 | -- Generators 10 | ------------------------------------------------------------------------------- 11 | 12 | arbAffineCircuit :: 13 | Arbitrary f => 14 | Int -> 15 | Int -> 16 | Gen (AffineCircuit Int f) 17 | arbAffineCircuit numVars size 18 | | size <= 0 = 19 | oneof $ 20 | [ ConstGate <$> arbitrary 21 | ] 22 | ++ if numVars > 0 23 | then [Var <$> choose (0, numVars - 1)] 24 | else [] 25 | | size > 0 = 26 | oneof 27 | [ ScalarMul <$> arbitrary <*> arbAffineCircuit numVars (size - 1), 28 | Add <$> arbAffineCircuit numVars (size - 1) 29 | <*> arbAffineCircuit numVars (size - 1) 30 | ] 31 | 32 | arbInputVector :: Arbitrary f => Int -> Gen (Map Int f) 33 | arbInputVector numVars = Map.fromList . zip [0 ..] <$> vector numVars 34 | 35 | -- | The input vector has to have the correct length, so we want to 36 | -- generate the program and the test input simultaneously. 37 | data AffineCircuitWithInputs f = AffineCircuitWithInputs (AffineCircuit Int f) [Map Int f] 38 | deriving (Show) 39 | 40 | instance Arbitrary f => Arbitrary (AffineCircuitWithInputs f) where 41 | arbitrary = do 42 | numVars <- abs <$> arbitrary 43 | program <- scale (`div` 7) $ sized (arbAffineCircuit numVars) 44 | inputs <- vectorOf 10 $ arbInputVector numVars 45 | pure $ AffineCircuitWithInputs program inputs 46 | 47 | ------------------------------------------------------------------------------- 48 | -- Tests 49 | ------------------------------------------------------------------------------- 50 | 51 | -- | Check that evaluating the vector representation of the circuit 52 | -- yields the same results as evaluating the circuit "directly". Field 53 | -- is instantiated as being the rationals for testing. It later should 54 | -- probably be something like Pairing.Fr.Fr. 55 | prop_affineCircuitToAffineMap :: 56 | AffineCircuitWithInputs Rational -> Bool 57 | prop_affineCircuitToAffineMap (AffineCircuitWithInputs program inputs) = 58 | all testInput inputs 59 | where 60 | testInput input = 61 | evalAffineCircuit Map.lookup input program 62 | == evalAffineMap (affineCircuitToAffineMap program) input 63 | -------------------------------------------------------------------------------- /tex/2f2322dff5bde89c37bcae4116fe20a8.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /tex/9b325b9e31e85137d1de765f43c0f8bc.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /package.yaml: -------------------------------------------------------------------------------- 1 | name : arithmetic-circuits 2 | version : 0.2.0 3 | synopsis : Arithmetic circuits for zkSNARKs 4 | description : Arithmetic circuits for zkSNARKs 5 | maintainer : Adjoint Inc (info@adjoint.io) 6 | license : MIT 7 | github : adjoint-io/arithmetic-circuits 8 | category : Cryptography 9 | 10 | default-extensions: 11 | - LambdaCase 12 | - OverloadedStrings 13 | - NoImplicitPrelude 14 | - GADTs 15 | 16 | dependencies: 17 | # Base 18 | - base >= 4.10 && < 5 19 | - protolude >= 0.2 && < 0.4 20 | - containers >= 0.6.0 && < 0.7 21 | - text >= 1.2.3 && < 1.3 22 | - vector >= 0.12 && < 0.13 23 | 24 | # Algebra 25 | - poly >= 0.3.2 && < 0.5 26 | - semirings >= 0.5.2 && < 0.6 27 | 28 | # Cryptography 29 | - bulletproofs >= 1.1.0 && < 1.2 30 | - elliptic-curve >= 0.3 && < 0.4 31 | - galois-fft >= 0.1.0 && < 0.2 32 | - galois-field >= 1.0.2 && < 2.0.0 33 | 34 | # Misc 35 | - filepath >= 1.4.2 && < 1.5 36 | - process-extras >= 0.7.4 && < 0.8 37 | - wl-pprint-text >= 1.2.0 && < 1.3 38 | - MonadRandom >= 0.5.1 && < 0.6 39 | 40 | # Serialization 41 | - aeson >= 1.4 && < 1.5 42 | 43 | extra-source-files: 44 | - README.md 45 | - ChangeLog.md 46 | 47 | ghc-options: 48 | - -freverse-errors 49 | - -O2 50 | - -Wall 51 | 52 | library: 53 | exposed-modules: 54 | - Circuit 55 | - Circuit.Affine 56 | - Circuit.Arithmetic 57 | - Circuit.Bulletproofs 58 | - Circuit.Dot 59 | - Circuit.Expr 60 | - Circuit.Lang 61 | - Fresh 62 | - QAP 63 | source-dirs: 64 | - src 65 | 66 | tests: 67 | circuit-tests: 68 | main: Main 69 | dependencies: 70 | - arithmetic-circuits 71 | - pairing >= 1.0 && < 1.1 72 | - QuickCheck >= 2.12 && < 2.14 73 | - quickcheck-instances >= 0.3 && < 0.4 74 | - tasty >= 1.2 && < 1.3 75 | - tasty-discover >= 4.2 && < 4.3 76 | - tasty-hunit >= 0.10 && < 0.11 77 | - tasty-quickcheck >= 0.10 && < 0.11 78 | source-dirs: 79 | - test 80 | 81 | readme-test: 82 | dependencies: 83 | - arithmetic-circuits 84 | - base >= 4.10 && < 5 85 | - pairing >= 1.0 && < 1.1 86 | - protolude >= 0.2 && < 0.4 87 | - markdown-unlit >= 0.5 && < 0.6 88 | main: README.lhs 89 | ghc-options: -pgmL markdown-unlit 90 | 91 | benchmarks: 92 | circuit-benchmarks: 93 | main: Main 94 | dependencies: 95 | - criterion >= 1.5 && < 1.6 96 | - pairing >= 1.0 && < 1.1 97 | - arithmetic-circuits 98 | source-dirs: 99 | - bench 100 | -------------------------------------------------------------------------------- /tex/89f2e0d2d24bcf44db73aab8fc03252c.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/Circuit/Dot.hs: -------------------------------------------------------------------------------- 1 | -- | Visualise circuits using Graphviz 2 | module Circuit.Dot 3 | ( arithCircuitToDot 4 | , dotWriteSVG 5 | ) where 6 | 7 | import Protolude 8 | 9 | import qualified Data.Text as Text 10 | import System.FilePath (replaceExtension) 11 | import System.Process.Text (readProcessWithExitCode) 12 | import Text.PrettyPrint.Leijen.Text (Pretty(..)) 13 | 14 | import Circuit.Affine () 15 | import Circuit.Arithmetic (ArithCircuit(..), Gate(..), Wire(..), fetchVars) 16 | 17 | arithCircuitToDot 18 | :: (Show f) => ArithCircuit f -> Text 19 | arithCircuitToDot (ArithCircuit gates) 20 | = Text.unlines . wrapInDigraph . concatMap graphGate $ gates 21 | where 22 | wrapInDigraph x = ["digraph g {"] ++ x ++ ["}"] 23 | 24 | dotWire :: Wire -> Text 25 | dotWire = show . pretty 26 | 27 | dotArrow :: Text -> Text -> Text 28 | dotArrow s t = s <> " -> " <> t 29 | 30 | dotArrowLabel :: Text -> Text -> Text -> Text 31 | dotArrowLabel s t lbl = dotArrow s t <> " [label=\"" <> lbl <> "\"]" 32 | 33 | labelNode lblId lbl = lblId <> " [label=\"" <> lbl <> "\"]" 34 | 35 | pointNode lblId = lblId <> " [shape=point]" 36 | 37 | graphGate :: Show f => Gate Wire f -> [Text] 38 | graphGate (Mul lhs rhs output) 39 | = [ labelNode gateLabel "*" 40 | , labelNode lhsLabel (show $ pretty lhs) 41 | , dotArrow lhsLabel gateLabel 42 | , labelNode rhsLabel (show $ pretty rhs) 43 | , dotArrow rhsLabel gateLabel 44 | ] ++ inputs lhs lhsLabel 45 | ++ inputs rhs rhsLabel 46 | where 47 | lhsLabel = dotWire output <> "lhs" 48 | rhsLabel = dotWire output <> "rhs" 49 | gateLabel = dotWire output 50 | inputs circuit tgt 51 | = map ((\src -> dotArrowLabel src tgt (show $ pretty src)) 52 | . dotWire) 53 | $ fetchVars circuit 54 | graphGate (Equal i m output) 55 | = [ labelNode gateLabel "= 0 ? 0 : 1" 56 | , dotArrowLabel (dotWire i) gateLabel (dotWire i) 57 | , dotArrowLabel (dotWire m) gateLabel (dotWire m) 58 | ] 59 | where 60 | gateLabel = dotWire output 61 | graphGate (Split i outputs) 62 | = [ labelNode gateLabel "split" 63 | , dotArrowLabel (dotWire i) gateLabel (dotWire i) 64 | ] ++ map (pointNode . dotWire) outputs 65 | ++ map (\output -> dotArrow gateLabel (dotWire output)) outputs 66 | where 67 | gateLabel = Text.concat . fmap dotWire $ outputs 68 | 69 | 70 | callDot :: Text -> IO Text 71 | callDot g = do 72 | (_, out, err) <- readProcessWithExitCode "dot" ["-Tsvg"] g 73 | if err == "" then pure out else panic err 74 | 75 | 76 | dotWriteSVG :: FilePath -> Text -> IO () 77 | dotWriteSVG path = callDot >=> writeFile (replaceExtension path ".svg") 78 | 79 | 80 | -------------------------------------------------------------------------------- /tex/d5c18a8ca1894fd3a7d25f242cbe8890.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /tex/2ad9d098b937e46f9f58968551adac57.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /tex/55a049b8f161ae7cfeb0197d75aff967.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /tex/2d4c6ac334688c42fb4089749e372345.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /tex/580e7a6446bf50562e34247c545883a2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /test/Test/QAP.hs: -------------------------------------------------------------------------------- 1 | module Test.QAP where 2 | 3 | import Protolude 4 | 5 | import Data.Map (Map) 6 | import qualified Data.Map as Map 7 | import Test.Tasty.HUnit 8 | import Test.Tasty.QuickCheck 9 | 10 | import Circuit.Affine 11 | import Circuit.Arithmetic 12 | import Data.Pairing.BN254 (Fr, getRootOfUnity) 13 | import QAP 14 | 15 | import Test.Circuit.Affine (arbAffineCircuit, arbInputVector) 16 | 17 | ------------------------------------------------------------------------------- 18 | -- Generators 19 | ------------------------------------------------------------------------------- 20 | 21 | arbGate 22 | :: Arbitrary f 23 | => Int -> Gen (Gate Wire f) 24 | arbGate numVars 25 | = oneof 26 | [ Mul <$> (mapVarsAffine InputWire <$> sized (arbAffineCircuit numVars)) 27 | <*> (mapVarsAffine InputWire <$> sized (arbAffineCircuit numVars)) 28 | <*> pure (OutputWire 0) 29 | , Equal <$> (InputWire <$> choose (0, numVars - 1)) 30 | <*> pure (IntermediateWire 0) 31 | <*> pure (OutputWire 0) 32 | ] 33 | 34 | data GateWithInputs f = GateWithInputs (Gate Wire f) [Map Int f] 35 | deriving Show 36 | 37 | instance Arbitrary f => Arbitrary (GateWithInputs f) where 38 | arbitrary = do 39 | numVars <- (+1) . abs <$> arbitrary 40 | program <- scale (`div` 7) $ arbGate numVars 41 | inputs <- vectorOf 10 (arbInputVector numVars) 42 | pure $ GateWithInputs program inputs 43 | 44 | ------------------------------------------------------------------------------- 45 | -- Test values 46 | ------------------------------------------------------------------------------- 47 | 48 | testArithCircuit :: ArithCircuit Fr 49 | testArithCircuit 50 | = ArithCircuit 51 | [ Mul (Var (InputWire 0)) (Var (InputWire 1)) (IntermediateWire 0) 52 | , Mul (Var (InputWire 2)) (Var (InputWire 3)) (IntermediateWire 1) 53 | , Mul (Add (ConstGate 10) (Var (IntermediateWire 0))) (Var (IntermediateWire 1)) (OutputWire 0) 54 | ] 55 | 56 | testVarsArithCircuit :: Map Int Fr 57 | testVarsArithCircuit 58 | = Map.fromList [ (0, 2) 59 | , (1, 3) 60 | , (2, 4) 61 | , (3, 5) 62 | ] 63 | 64 | ------------------------------------------------------------------------------- 65 | -- Tests 66 | ------------------------------------------------------------------------------- 67 | 68 | unit_arithCircuitToQapCorrect :: Assertion 69 | unit_arithCircuitToQapCorrect 70 | = assertBool "Verifying assignment against QAP of circuit failed" 71 | $ verifyAssignment qap assignment 72 | where 73 | roots = [[7],[8],[9]] 74 | qap = arithCircuitToQAP roots testArithCircuit 75 | assignment = generateAssignment testArithCircuit testVarsArithCircuit 76 | 77 | unit_arithCircuitToQapNoFalsePositive :: Assertion 78 | unit_arithCircuitToQapNoFalsePositive 79 | = assertBool "Verifying whether verification fails on faulty assignment" 80 | $ not $ verifyAssignment qap invalidAssignment 81 | where 82 | roots = [[7],[8],[9]] 83 | qap = arithCircuitToQAP roots testArithCircuit 84 | invalidAssignment 85 | = QapSet 86 | { qapSetConstant = 1 87 | , qapSetInput = Map.fromList [(0,2),(1,3),(2,4),(3,5)] 88 | , qapSetIntermediate = Map.fromList [(0,7),(1,20)] 89 | , qapSetOutput = Map.fromList [(0,320)] 90 | } 91 | 92 | prop_gateToQapCorrect 93 | :: GateWithInputs Fr -> Bool 94 | prop_gateToQapCorrect (GateWithInputs program inputs) 95 | = all testInput inputs 96 | where 97 | roots = case program of 98 | Mul {} -> [1] 99 | Equal {} -> [1,2] 100 | _ -> panic "Invalid roots" 101 | qap = gateToQAP getRootOfUnity roots program 102 | assignment input = generateAssignmentGate program input 103 | testInput = verifyAssignment qap . assignment 104 | -------------------------------------------------------------------------------- /stack.yaml.lock: -------------------------------------------------------------------------------- 1 | # This file was autogenerated by Stack. 2 | # You should not edit this file by hand. 3 | # For more information, please see the documentation at: 4 | # https://docs.haskellstack.org/en/stable/lock_files 5 | 6 | packages: 7 | - completed: 8 | hackage: elliptic-curve-0.3.0@sha256:7d314ff56011de62370bdd1b840866d1e5ce061972e54be673f5768cbf8d3d42,7072 9 | pantry-tree: 10 | size: 9500 11 | sha256: 650191792bfc8b119e8a0ab1103383ddfa395ed692573519c27cc3d198089cb5 12 | original: 13 | hackage: elliptic-curve-0.3.0 14 | - completed: 15 | hackage: galois-field-1.0.2@sha256:ecfdd72259a3db7dc89ddeaa5c5dd7d7bcc305c0450a89c757d93fe2ddb71c77,4685 16 | pantry-tree: 17 | size: 1451 18 | sha256: 4b3eecd9ca434ee37785a1f3c4312c76760d9b79de05048fa17b0878c30f503c 19 | original: 20 | hackage: galois-field-1.0.2 21 | - completed: 22 | hackage: poly-0.4.0.0@sha256:75c243113712745dab5d2f4a52643705e146473770af212f9e0ce3d87a83f2ed,2123 23 | pantry-tree: 24 | size: 1896 25 | sha256: 06f435980848905d2bcf23b85782863b1608f7a78dbdaea42e33a2e865adf476 26 | original: 27 | hackage: poly-0.4.0.0 28 | - completed: 29 | hackage: semirings-0.5.3@sha256:4b0d243f88bb07c0673978a37b6b9fd16da44f15e8d552fb08795d9a88a3dc76,3791 30 | pantry-tree: 31 | size: 610 32 | sha256: 0bc3f63a3b92be1c6be2e36c7db8df62e61b2860eba7f083b84e2b16dbaa2d69 33 | original: 34 | hackage: semirings-0.5.3 35 | - completed: 36 | hackage: pairing-1.0.0@sha256:709d2e1a98b18327d2fba4efaa6069ef783333c95055ad73416e9401d02000ac,3507 37 | pantry-tree: 38 | size: 1693 39 | sha256: 25c968cec9049b268af6445dd42465fa49b4c294dbe10207b72d9f301c76ea5e 40 | original: 41 | hackage: pairing-1.0.0 42 | - completed: 43 | hackage: arithmoi-0.8.0.0@sha256:31c4bc158d3ce73d78f7037666bcba3e5374b2d5a0ea1cb0c50aaad85857c6b2,7627 44 | pantry-tree: 45 | size: 10170 46 | sha256: 960e3112065772be46a63c6f16799df7e992002bff800d44d683d2969ffa71b9 47 | original: 48 | hackage: arithmoi-0.8.0.0 49 | - completed: 50 | hackage: bulletproofs-1.1.0@sha256:eebdf3963372e40675fd501f3f3cc6f553d3925965ce38a112998a321cc7f6f2,5395 51 | pantry-tree: 52 | size: 2191 53 | sha256: 4337652dac76bfaea64e7d5498293df616055c89f67c18cdd0f182be98c9a355 54 | original: 55 | hackage: bulletproofs-1.1.0 56 | - completed: 57 | hackage: galois-fft-0.1.0@sha256:6a1e1e255c236b521379435ba528bbb5bc2a159c3ebb7fb960090cd5cf950b20,2409 58 | pantry-tree: 59 | size: 472 60 | sha256: 8bc255dfd2276220edb94f6c3139b204e66fe85d612e24f80ff63a757da64cc4 61 | original: 62 | hackage: galois-fft-0.1.0 63 | - completed: 64 | hackage: vector-algorithms-0.8.0.3@sha256:477ef5ac82fdd28a39536ed0a767faec3425802827abee485be31db5bc6f5e90,3488 65 | pantry-tree: 66 | size: 1439 67 | sha256: 9f04ef44c2459a122f2d5c093b4a9ebc47aaa00284a7bff9793fee7c6783f979 68 | original: 69 | hackage: vector-algorithms-0.8.0.3 70 | - completed: 71 | hackage: bitvec-1.0.3.0@sha256:f69ed0e463045cb497a7cf1bc808a2e84ea0ce286cf9507983bb6ed8b4bd3993,3977 72 | pantry-tree: 73 | size: 2372 74 | sha256: e3cf4b28e01c3eb9b0cf46a8a2e7eef1ced133d5ad626aaba099cf468007dd90 75 | original: 76 | hackage: bitvec-1.0.3.0 77 | - completed: 78 | hackage: mod-0.1.1.0@sha256:57f230d5664b1c311d1044c259ef880875e4b3bcbfc7c57eab3bf03fce87f445,1838 79 | pantry-tree: 80 | size: 464 81 | sha256: ca61450dd1d84e6beefba010a7032dfb5a8bc2e8344a369430992c56ec083ad7 82 | original: 83 | hackage: mod-0.1.1.0 84 | - completed: 85 | hackage: QuickCheck-2.13.2@sha256:ad4e5adbd1c9dc0221a44307b992cb040c515f31095182e47aa7e974bc461df1,6952 86 | pantry-tree: 87 | size: 2202 88 | sha256: f79eee2f6a00b2c649f993a7b358827702373cbc931ced55ebdfb59625540403 89 | original: 90 | hackage: QuickCheck-2.13.2 91 | snapshots: 92 | - completed: 93 | size: 523700 94 | url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/14/7.yaml 95 | sha256: 8e3f3c894be74d71fa4bf085e0a8baae7e4d7622d07ea31a52736b80f8b9bb1a 96 | original: lts-14.7 97 | -------------------------------------------------------------------------------- /test/Test/Circuit/Expr.hs: -------------------------------------------------------------------------------- 1 | module Test.Circuit.Expr where 2 | 3 | import Circuit.Arithmetic 4 | import Circuit.Expr 5 | import Data.Curve.Weierstrass.BN254 (Fr) 6 | import qualified Data.Map as Map 7 | import Fresh 8 | import Protolude 9 | import QAP 10 | import Test.Circuit.Affine 11 | import Test.Tasty.QuickCheck 12 | 13 | ------------------------------------------------------------------------------- 14 | -- Generators 15 | ------------------------------------------------------------------------------- 16 | 17 | arbExprBool :: Arbitrary f => Int -> Int -> Gen (Expr Int f Bool) 18 | arbExprBool numVars size 19 | | size <= 0 = oneof $ [EConstBool <$> arbitrary] ++ if numVars > 0 20 | then [] 21 | else [] 22 | | size > 0 = oneof 23 | [ EBinOp BAnd <$> arbExprBool numVars (size - 1) <*> arbExprBool 24 | numVars 25 | (size - 1) 26 | , EBinOp BOr <$> arbExprBool numVars (size - 1) <*> arbExprBool numVars 27 | (size - 1) 28 | , EUnOp UNot <$> arbExprBool numVars (size - 1) 29 | , EEq <$> arbExpr numVars (size - 1) 30 | <*> arbExpr numVars (size - 1) 31 | ] 32 | 33 | arbExpr :: Arbitrary f => Int -> Int -> Gen (Expr Int f f) 34 | arbExpr numVars size 35 | | size <= 0 = oneof $ [EConst <$> arbitrary] ++ if numVars > 0 36 | then [EVar <$> choose (0, numVars - 1)] 37 | else [] 38 | | size > 0 = oneof 39 | [ EBinOp BAdd <$> arbExpr numVars (size - 1) <*> arbExpr numVars (size - 1) 40 | , EBinOp BSub <$> arbExpr numVars (size - 1) <*> arbExpr numVars (size - 1) 41 | , EBinOp BMul <$> arbExpr numVars (size - 1) <*> arbExpr numVars (size - 1) 42 | , EUnOp UNeg <$> arbExpr numVars (size - 1) 43 | , EIf 44 | <$> arbExprBool numVars (size - 1) 45 | <*> arbExpr numVars (size - 1) 46 | <*> arbExpr numVars (size - 1) 47 | ] 48 | 49 | 50 | data ExprWithInputs f = ExprWithInputs (Expr Int f f) [Map Int f] 51 | deriving Show 52 | 53 | 54 | instance Arbitrary f => Arbitrary (ExprWithInputs f) where 55 | arbitrary = do 56 | numVars <- abs <$> arbitrary 57 | program <- scale (`div` 10) $ sized (arbExpr numVars) 58 | inputs <- vectorOf 5 $ arbInputVector numVars 59 | pure $ ExprWithInputs program inputs 60 | 61 | ------------------------------------------------------------------------------- 62 | -- Tests 63 | ------------------------------------------------------------------------------- 64 | 65 | 66 | -- | Check whether exprToArithCircuit produces valid circuits 67 | prop_compiledCircuitValid :: ExprWithInputs Fr -> Bool 68 | prop_compiledCircuitValid (ExprWithInputs expr _) = 69 | validArithCircuit (execCircuitBuilder $ exprToArithCircuit expr (OutputWire 0)) 70 | 71 | -- | Check whether exprToArithCircuit produces circuits that have valid assignments to the qap 72 | prop_compiledQAPValid :: ExprWithInputs Fr -> Property 73 | prop_compiledQAPValid (ExprWithInputs expr inputs) = withMaxSuccess 50 74 | $ all testInput inputs 75 | where 76 | circuit = (execCircuitBuilder $ exprToArithCircuit expr (OutputWire 0)) 77 | roots :: ArithCircuit Fr -> [[Fr]] 78 | roots = evalFresh . generateRoots (fromIntegral <$> fresh) 79 | qap = arithCircuitToQAP (roots circuit) circuit 80 | assignment input = generateAssignment circuit input 81 | testInput = verifyAssignment qap . assignment 82 | 83 | -- | Check whether evaluating an expression and 84 | -- evaluating the arithmetic circuit translation produces the same 85 | -- result 86 | prop_evalEqArithEval :: ExprWithInputs Fr -> Bool 87 | prop_evalEqArithEval (ExprWithInputs expr inputs) = all testInput inputs 88 | where 89 | testInput input = exprResult input == arithResult input 90 | exprResult input = evalExpr (Map.lookup) expr input 91 | arithResult input = arithOutput input Map.! (OutputWire 0) 92 | arithOutput input = evalArithCircuit (Map.lookup) 93 | (Map.insert) 94 | circuit 95 | (Map.mapKeys InputWire input) 96 | circuit = (execCircuitBuilder $ exprToArithCircuit expr (OutputWire 0)) 97 | -------------------------------------------------------------------------------- /tex/2441df23627a504b2a4c6f5006893fd6.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/Circuit/Affine.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DeriveAnyClass, DeriveGeneric, LambdaCase, StrictData #-} 2 | 3 | -- | Definition of arithmetic circuits that only contain addition, 4 | -- scalar multiplications and constant gates, along with its direct 5 | -- evaluation and translation into affine maps. 6 | module Circuit.Affine 7 | ( AffineCircuit (..), 8 | collectInputsAffine, 9 | mapVarsAffine, 10 | evalAffineCircuit, 11 | affineCircuitToAffineMap, 12 | evalAffineMap, 13 | dotProduct, 14 | ) 15 | where 16 | 17 | import Data.Aeson (FromJSON, ToJSON) 18 | import Data.Map (Map) 19 | import qualified Data.Map as Map 20 | import Protolude 21 | import Text.PrettyPrint.Leijen.Text (Doc, Pretty(..), parens, text, 22 | (<+>)) 23 | 24 | -- | Arithmetic circuits without multiplication, i.e. circuits 25 | -- describe affine transformations. 26 | data AffineCircuit i f 27 | = Add (AffineCircuit i f) (AffineCircuit i f) 28 | | ScalarMul f (AffineCircuit i f) 29 | | ConstGate f 30 | | Var i 31 | deriving (Read, Eq, Show, Generic, NFData, FromJSON, ToJSON) 32 | 33 | collectInputsAffine :: Ord i => AffineCircuit i f -> [i] 34 | collectInputsAffine = \case 35 | Add l r -> collectInputsAffine l ++ collectInputsAffine r 36 | ScalarMul _ x -> collectInputsAffine x 37 | ConstGate _ -> [] 38 | Var i -> [i] 39 | 40 | instance (Pretty i, Show f) => Pretty (AffineCircuit i f) where 41 | pretty = prettyPrec 0 42 | where 43 | prettyPrec :: (Pretty i, Show f) => Int -> AffineCircuit i f -> Doc 44 | prettyPrec p e = 45 | case e of 46 | Var v -> 47 | pretty v 48 | ConstGate f -> 49 | text $ show f 50 | ScalarMul f e1 -> 51 | text (show f) <+> text "*" <+> parensPrec 7 p (prettyPrec p e1) 52 | Add e1 e2 -> 53 | parensPrec 6 p $ 54 | prettyPrec 6 e1 55 | <+> text "+" 56 | <+> prettyPrec 6 e2 57 | 58 | parensPrec :: Int -> Int -> Doc -> Doc 59 | parensPrec opPrec p = if p > opPrec then parens else identity 60 | 61 | -- | Apply mapping to variable names, i.e. rename variables. (Ideally 62 | -- the mapping is injective.) 63 | mapVarsAffine :: (i -> j) -> AffineCircuit i f -> AffineCircuit j f 64 | mapVarsAffine f = \case 65 | Add l r -> Add (mapVarsAffine f l) (mapVarsAffine f r) 66 | ScalarMul s expr -> ScalarMul s $ mapVarsAffine f expr 67 | ConstGate c -> ConstGate c 68 | Var i -> Var $ f i 69 | 70 | -- | Evaluate the arithmetic circuit without mul-gates on the given 71 | -- input. Variable map is assumed to have all the variables referred 72 | -- to in the circuit. Failed lookups are currently treated as 0. 73 | evalAffineCircuit :: 74 | Num f => 75 | -- | lookup function for variable mapping 76 | (i -> vars -> Maybe f) -> 77 | -- | variables 78 | vars -> 79 | -- | circuit to evaluate 80 | AffineCircuit i f -> 81 | f 82 | evalAffineCircuit lookupVar vars = \case 83 | ConstGate f -> f 84 | Var i -> fromMaybe 0 $ lookupVar i vars 85 | Add l r -> evalAffineCircuit lookupVar vars l + evalAffineCircuit lookupVar vars r 86 | ScalarMul scalar expr -> evalAffineCircuit lookupVar vars expr * scalar 87 | 88 | -- | Convert non-mul circuit to a vector representing the evaluation 89 | -- function. We use a @Map@ to represent the potentially sparse vector. 90 | affineCircuitToAffineMap :: 91 | (Num f, Ord i) => 92 | -- | circuit to translate 93 | AffineCircuit i f -> 94 | -- | constant part and non-constant part 95 | (f, Map i f) 96 | affineCircuitToAffineMap = \case 97 | Var i -> (0, Map.singleton i 1) 98 | Add l r -> (constLeft + constRight, Map.unionWith (+) vecLeft vecRight) 99 | where 100 | (constLeft, vecLeft) = affineCircuitToAffineMap l 101 | (constRight, vecRight) = affineCircuitToAffineMap r 102 | ScalarMul scalar expr -> (scalar * constExpr, fmap (scalar *) vecExpr) 103 | where 104 | (constExpr, vecExpr) = affineCircuitToAffineMap expr 105 | ConstGate f -> (f, Map.empty) 106 | 107 | -- | Evaluating the affine map representing the arithmetic circuit 108 | -- without mul-gates against inputs. If the input map does not have a 109 | -- variable that is referred to in the affine map, then it is treated 110 | -- as a 0. 111 | evalAffineMap :: 112 | (Num f, Ord i) => 113 | -- | program split into constant and non-constant part 114 | (f, Map i f) -> 115 | -- | input variables 116 | Map i f -> 117 | f 118 | evalAffineMap (constPart, linearPart) input = 119 | constPart + dotProduct linearPart input 120 | 121 | dotProduct :: (Num f, Ord i) => Map i f -> Map i f -> f 122 | dotProduct inp comp = 123 | sum 124 | . Map.elems 125 | $ Map.mapWithKey (\ix c -> c * Map.findWithDefault 0 ix inp) comp 126 | -------------------------------------------------------------------------------- /tex/5e27bca98285ab8eccf4d53506baeaec.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /tex/a420c52aa24a502d60aef830b3b45f9f.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /tex/9000cff3d46536c190fabb076ebe7cbb.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /tex/7592e8ca3cc64009a29ef0fb58f65c76.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /tex/52be0087c9da1f0683ccc50761e8bcab.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /tex/b2584d6517f9c72bcd800016d8d1fa0d.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /tex/083da1124b81d709f20f2575ae9138c3.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /arithmetic-circuits.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: 1.12 2 | 3 | -- This file has been generated from package.yaml by hpack version 0.31.2. 4 | -- 5 | -- see: https://github.com/sol/hpack 6 | -- 7 | -- hash: b73ab8e6731b0ff5f4b4c0c9bd39948780d65c323746630293312288b155475e 8 | 9 | name: arithmetic-circuits 10 | version: 0.2.0 11 | synopsis: Arithmetic circuits for zkSNARKs 12 | description: Arithmetic circuits for zkSNARKs 13 | category: Cryptography 14 | homepage: https://github.com/adjoint-io/arithmetic-circuits#readme 15 | bug-reports: https://github.com/adjoint-io/arithmetic-circuits/issues 16 | maintainer: Adjoint Inc (info@adjoint.io) 17 | license: MIT 18 | license-file: LICENSE 19 | build-type: Simple 20 | extra-source-files: 21 | README.md 22 | ChangeLog.md 23 | 24 | source-repository head 25 | type: git 26 | location: https://github.com/adjoint-io/arithmetic-circuits 27 | 28 | library 29 | exposed-modules: 30 | Circuit 31 | Circuit.Affine 32 | Circuit.Arithmetic 33 | Circuit.Bulletproofs 34 | Circuit.Dot 35 | Circuit.Expr 36 | Circuit.Lang 37 | Fresh 38 | QAP 39 | 40 | other-modules: Paths_arithmetic_circuits 41 | hs-source-dirs: src 42 | default-extensions: 43 | NoImplicitPrelude 44 | GADTs 45 | LambdaCase 46 | OverloadedStrings 47 | 48 | ghc-options: -freverse-errors -O2 -Wall 49 | build-depends: 50 | aeson >=1.4 && <1.5 51 | , base >=4.10 && <5 52 | , bulletproofs >=1.1.0 && <1.2 53 | , containers >=0.6.0 && <0.7 54 | , elliptic-curve >=0.3 && <0.4 55 | , filepath >=1.4.2 && <1.5 56 | , galois-fft >=0.1.0 && <0.2 57 | , galois-field >=1.0.2 && <2.0.0 58 | , MonadRandom >=0.5.1 && <0.6 59 | , poly >=0.3.2 && <0.5 60 | , process-extras >=0.7.4 && <0.8 61 | , protolude >=0.2 && <0.4 62 | , semirings >=0.5.2 && <0.6 63 | , text >=1.2.3 && <1.3 64 | , vector >=0.12 && <0.13 65 | , wl-pprint-text >=1.2.0 && <1.3 66 | 67 | default-language: Haskell2010 68 | 69 | test-suite circuit-tests 70 | type: exitcode-stdio-1.0 71 | main-is: Main.hs 72 | other-modules: 73 | Paths_arithmetic_circuits 74 | Test.Circuit.Affine 75 | Test.Circuit.Arithmetic 76 | Test.Circuit.Expr 77 | Test.QAP 78 | 79 | hs-source-dirs: test 80 | default-extensions: 81 | NoImplicitPrelude 82 | GADTs 83 | LambdaCase 84 | OverloadedStrings 85 | 86 | ghc-options: -freverse-errors -O2 -Wall -main-is Main 87 | build-depends: 88 | aeson >=1.4 && <1.5 89 | , arithmetic-circuits 90 | , base >=4.10 && <5 91 | , bulletproofs >=1.1.0 && <1.2 92 | , containers >=0.6.0 && <0.7 93 | , elliptic-curve >=0.3 && <0.4 94 | , filepath >=1.4.2 && <1.5 95 | , galois-fft >=0.1.0 && <0.2 96 | , galois-field >=1.0.2 && <2.0.0 97 | , MonadRandom >=0.5.1 && <0.6 98 | , pairing >=1.0 && <1.1 99 | , poly >=0.3.2 && <0.5 100 | , process-extras >=0.7.4 && <0.8 101 | , protolude >=0.2 && <0.4 102 | , QuickCheck >=2.12 && <2.14 103 | , quickcheck-instances >=0.3 && <0.4 104 | , semirings >=0.5.2 && <0.6 105 | , tasty >=1.2 && <1.3 106 | , tasty-discover >=4.2 && <4.3 107 | , tasty-hunit >=0.10 && <0.11 108 | , tasty-quickcheck >=0.10 && <0.11 109 | , text >=1.2.3 && <1.3 110 | , vector >=0.12 && <0.13 111 | , wl-pprint-text >=1.2.0 && <1.3 112 | 113 | default-language: Haskell2010 114 | 115 | test-suite readme-test 116 | type: exitcode-stdio-1.0 117 | main-is: README.lhs 118 | other-modules: Paths_arithmetic_circuits 119 | default-extensions: 120 | NoImplicitPrelude 121 | GADTs 122 | LambdaCase 123 | OverloadedStrings 124 | 125 | ghc-options: -freverse-errors -O2 -Wall -pgmL markdown-unlit 126 | build-depends: 127 | aeson >=1.4 && <1.5 128 | , arithmetic-circuits 129 | , base >=4.10 && <5 130 | , bulletproofs >=1.1.0 && <1.2 131 | , containers >=0.6.0 && <0.7 132 | , elliptic-curve >=0.3 && <0.4 133 | , filepath >=1.4.2 && <1.5 134 | , galois-fft >=0.1.0 && <0.2 135 | , galois-field >=1.0.2 && <2.0.0 136 | , markdown-unlit >=0.5 && <0.6 137 | , MonadRandom >=0.5.1 && <0.6 138 | , pairing >=1.0 && <1.1 139 | , poly >=0.3.2 && <0.5 140 | , process-extras >=0.7.4 && <0.8 141 | , protolude >=0.2 && <0.4 142 | , semirings >=0.5.2 && <0.6 143 | , text >=1.2.3 && <1.3 144 | , vector >=0.12 && <0.13 145 | , wl-pprint-text >=1.2.0 && <1.3 146 | 147 | default-language: Haskell2010 148 | 149 | benchmark circuit-benchmarks 150 | type: exitcode-stdio-1.0 151 | main-is: Main.hs 152 | other-modules: 153 | Circuit 154 | Paths_arithmetic_circuits 155 | 156 | hs-source-dirs: bench 157 | default-extensions: 158 | NoImplicitPrelude 159 | GADTs 160 | LambdaCase 161 | OverloadedStrings 162 | 163 | ghc-options: -freverse-errors -O2 -Wall -main-is Main 164 | build-depends: 165 | aeson >=1.4 && <1.5 166 | , arithmetic-circuits 167 | , base >=4.10 && <5 168 | , bulletproofs >=1.1.0 && <1.2 169 | , containers >=0.6.0 && <0.7 170 | , criterion >=1.5 && <1.6 171 | , elliptic-curve >=0.3 && <0.4 172 | , filepath >=1.4.2 && <1.5 173 | , galois-fft >=0.1.0 && <0.2 174 | , galois-field >=1.0.2 && <2.0.0 175 | , MonadRandom >=0.5.1 && <0.6 176 | , pairing >=1.0 && <1.1 177 | , poly >=0.3.2 && <0.5 178 | , process-extras >=0.7.4 && <0.8 179 | , protolude >=0.2 && <0.4 180 | , semirings >=0.5.2 && <0.6 181 | , text >=1.2.3 && <1.3 182 | , vector >=0.12 && <0.13 183 | , wl-pprint-text >=1.2.0 && <1.3 184 | 185 | default-language: Haskell2010 186 | -------------------------------------------------------------------------------- /tex/d1dd493c98f06e9ef29b5fdc411e29f8.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /.assets/arithmetic-circuit-example.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | g 9 | 10 | 11 | 12 | imm_3 13 | 14 | * 15 | 16 | 17 | 18 | imm_4lhs 19 | 20 | imm_3 21 | 22 | 23 | 24 | imm_3->imm_4lhs 25 | 26 | 27 | imm_3 28 | 29 | 30 | 31 | imm_3lhs 32 | 33 | input_0 34 | 35 | 36 | 37 | imm_3lhs->imm_3 38 | 39 | 40 | 41 | 42 | 43 | imm_3rhs 44 | 45 | input_1 46 | 47 | 48 | 49 | imm_3rhs->imm_3 50 | 51 | 52 | 53 | 54 | 55 | input_0 56 | 57 | input_0 58 | 59 | 60 | 61 | input_0->imm_3lhs 62 | 63 | 64 | input_0 65 | 66 | 67 | 68 | imm_4rhs 69 | 70 | input_0 + input_2 71 | 72 | 73 | 74 | input_0->imm_4rhs 75 | 76 | 77 | input_0 78 | 79 | 80 | 81 | input_1 82 | 83 | input_1 84 | 85 | 86 | 87 | input_1->imm_3rhs 88 | 89 | 90 | input_1 91 | 92 | 93 | 94 | imm_4 95 | 96 | * 97 | 98 | 99 | 100 | imm_4lhs->imm_4 101 | 102 | 103 | 104 | 105 | 106 | imm_4rhs->imm_4 107 | 108 | 109 | 110 | 111 | 112 | input_2 113 | 114 | input_2 115 | 116 | 117 | 118 | input_2->imm_4rhs 119 | 120 | 121 | input_2 122 | 123 | 124 | -------------------------------------------------------------------------------- /tex/bf01d477a12bededfd9d65617fedacc9.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /test/Test/Circuit/Arithmetic.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DeriveAnyClass, DeriveGeneric, TupleSections #-} 2 | 3 | module Test.Circuit.Arithmetic where 4 | 5 | import Circuit.Affine 6 | import Circuit.Arithmetic 7 | import Data.Curve.Weierstrass.BN254 (Fr) 8 | import Data.Map (Map) 9 | import qualified Data.Map as Map 10 | import Data.Pairing.BN254 (getRootOfUnity) 11 | import Fresh 12 | import Protolude 13 | import QAP 14 | import Test.Tasty.HUnit 15 | import Test.Tasty.QuickCheck 16 | 17 | ------------------------------------------------------------------------------- 18 | -- Test values 19 | ------------------------------------------------------------------------------- 20 | 21 | testEqualCircuit :: ArithCircuit Fr 22 | testEqualCircuit = ArithCircuit [Equal (InputWire 0) (IntermediateWire 0) (OutputWire 0)] 23 | 24 | testInputMap :: Fr -> Map Int Fr 25 | testInputMap = Map.singleton 0 26 | 27 | testSplitUnsplitCircuit :: Int -> ArithCircuit Fr 28 | testSplitUnsplitCircuit nbits = 29 | ArithCircuit 30 | [ Split (InputWire 0) midWires, 31 | Mul (ConstGate 1) (unsplit midWires) (OutputWire 0) 32 | ] 33 | where 34 | midWires = fmap IntermediateWire [0 .. nbits - 1] 35 | 36 | ------------------------------------------------------------------------------- 37 | -- Generators 38 | ------------------------------------------------------------------------------- 39 | 40 | arbVars :: Int -> [Int] -> [Gen (AffineCircuit Wire f)] 41 | arbVars numInps mids = 42 | varInps numInps ++ varMids mids 43 | where 44 | varInps size 45 | | size <= 0 = [] 46 | | otherwise = [Var . InputWire <$> choose (0, numInps - 1)] 47 | varMids [] = [] 48 | varMids ms@(_ : _) = [Var . IntermediateWire <$> elements ms] 49 | 50 | arbAffineCircuitWithMids :: 51 | Arbitrary f => 52 | Int -> 53 | [Int] -> 54 | Int -> 55 | Gen (AffineCircuit Wire f) 56 | arbAffineCircuitWithMids numInps mids size 57 | | size <= 0 = 58 | oneof $ [ConstGate <$> arbitrary] ++ arbVars numInps mids 59 | | size > 0 = 60 | oneof 61 | [ ScalarMul <$> arbitrary <*> arbAffineCircuitWithMids numInps mids (size - 1), 62 | Add <$> arbAffineCircuitWithMids numInps mids (size - 1) 63 | <*> arbAffineCircuitWithMids numInps mids (size - 1) 64 | ] 65 | 66 | arbInputVector :: (Arbitrary f) => Int -> Gen (Map Int f) 67 | arbInputVector numVars = Map.fromList . zip [0 ..] <$> vector numVars 68 | 69 | arbArithCircuit :: 70 | Arbitrary f => 71 | -- | distribution of frequency of mul/equal/split 72 | -- gates, respectively 73 | (Int, Int, Int) -> 74 | Int -> 75 | Int -> 76 | Gen (ArithCircuit f) 77 | arbArithCircuit (distMul, distEqual, distSplit) numInps size 78 | | size <= 0 = 79 | pure $ ArithCircuit [] 80 | | size > 0 = 81 | do 82 | ArithCircuit gates <- arbArithCircuit (distMul, distEqual, distSplit) numInps (size - 1) 83 | let mids = [i | IntermediateWire i <- concatMap outputWires gates] 84 | frequency . catMaybes $ 85 | [ (distMul,) <$> mulGate gates mids, 86 | (distEqual,) <$> equalGate gates mids, 87 | (distSplit,) <$> splitGate gates mids 88 | ] 89 | where 90 | mulGate gates mids = 91 | Just $ do 92 | lhs <- arbAffineCircuitWithMids numInps mids 1 93 | rhs <- arbAffineCircuitWithMids numInps mids 1 94 | let outWire = case mids of 95 | [] -> 0 96 | ms@(_ : _) -> maximum ms + 1 97 | gate = Mul lhs rhs (IntermediateWire outWire) 98 | pure . ArithCircuit $ gates ++ [gate] 99 | equalGate _ [] = 100 | Nothing 101 | equalGate gates mids@(_ : _) = 102 | Just $ do 103 | inp <- elements mids 104 | let outWire = 105 | case mids of 106 | [] -> 0 107 | ms@(_ : _) -> maximum ms + 1 108 | gate = 109 | Equal 110 | (IntermediateWire inp) 111 | (IntermediateWire outWire) 112 | (IntermediateWire $ outWire + 1) 113 | pure . ArithCircuit $ gates ++ [gate] 114 | splitGate _ [] = 115 | Nothing 116 | splitGate gates mids@(_ : _) = 117 | Just $ do 118 | inp <- IntermediateWire <$> elements mids 119 | let firstOutWire = 120 | case mids of 121 | [] -> 0 122 | ms@(_ : _) -> maximum ms + 1 123 | nbits = 256 124 | outWires = fmap IntermediateWire [firstOutWire .. firstOutWire + nbits - 1] 125 | gate = Split inp outWires 126 | pure . ArithCircuit $ gates ++ [gate] 127 | 128 | -- | The input vector has to have the correct length, so we want to 129 | -- generate the program and the test input simultaneously. 130 | data ArithCircuitWithInputs f = ArithCircuitWithInputs (ArithCircuit f) [Map Int f] 131 | deriving (Show, Generic, NFData) 132 | 133 | instance (Arbitrary f, Num f) => Arbitrary (ArithCircuitWithInputs f) where 134 | arbitrary = do 135 | numVars <- abs <$> arbitrary `suchThat` (> 0) 136 | program <- sized (arbArithCircuit (50, 10, 1) numVars) 137 | inputs <- vectorOf 5 $ arbInputVector numVars 138 | pure $ ArithCircuitWithInputs program inputs 139 | 140 | data ArithCircuitWithInput f = ArithCircuitWithInput (ArithCircuit f) (Map Int f) 141 | deriving (Show, Generic, NFData) 142 | 143 | instance (Arbitrary f, Num f) => Arbitrary (ArithCircuitWithInput f) where 144 | arbitrary = do 145 | numVars <- abs <$> arbitrary `suchThat` (> 0) 146 | program <- sized (arbArithCircuit (50, 10, 1) numVars) 147 | input <- arbInputVector numVars 148 | pure $ ArithCircuitWithInput program input 149 | 150 | ------------------------------------------------------------------------------- 151 | -- Tests 152 | ------------------------------------------------------------------------------- 153 | 154 | unit_eqGate :: 155 | Assertion 156 | unit_eqGate = 157 | do 158 | testEvalWith 0 @?= Just 0 159 | testEvalWith 1 @?= Just 1 160 | testEvalWith 2 @?= Just 1 161 | testEvalWith 3 @?= Just 1 162 | where 163 | testEvalWith n = 164 | lookupAtWire (OutputWire 0) $ 165 | evalArithCircuit 166 | lookupAtWire 167 | updateAtWire 168 | testEqualCircuit 169 | (initialQapSet $ testInputMap n) 170 | 171 | unit_splitUnsplit :: Assertion 172 | unit_splitUnsplit = 173 | mapM_ (\n -> testSplitUnsplit n @?= Just n) (fmap fromIntegral [0 .. 2 ^ nbits - 1]) 174 | where 175 | nbits = 16 176 | testSplitUnsplit n = 177 | lookupAtWire (OutputWire 0) $ 178 | evalArithCircuit 179 | lookupAtWire 180 | updateAtWire 181 | (testSplitUnsplitCircuit nbits) 182 | (initialQapSet $ testInputMap n) 183 | 184 | prop_arithCircuitValid :: ArithCircuitWithInputs Fr -> Bool 185 | prop_arithCircuitValid (ArithCircuitWithInputs program _) = 186 | validArithCircuit program 187 | 188 | prop_arithCircuitToQAP_slow :: ArithCircuitWithInputs Fr -> Property 189 | prop_arithCircuitToQAP_slow (ArithCircuitWithInputs program inputs) = 190 | withMaxSuccess 10 $ 191 | case program of 192 | ArithCircuit [] -> True 193 | ArithCircuit (_ : _) -> all testInput inputs 194 | where 195 | roots = evalFresh $ generateRoots (fromIntegral . (+ 1) <$> fresh) program 196 | qap = arithCircuitToQAP roots program 197 | testInput input = 198 | verifyAssignment qap $ generateAssignment program input 199 | 200 | prop_arithCircuitToQAP_fft :: ArithCircuitWithInputs Fr -> Property 201 | prop_arithCircuitToQAP_fft (ArithCircuitWithInputs program inputs) = 202 | withMaxSuccess 10 $ 203 | case program of 204 | ArithCircuit [] -> True 205 | ArithCircuit (_ : _) -> all testInput inputs 206 | where 207 | roots = evalFresh $ generateRoots (fromIntegral . (+ 1) <$> fresh) program 208 | qap = createPolynomialsFFT getRootOfUnity $ arithCircuitToGenQAP roots program 209 | testInput input = verifyAssignment qap $ generateAssignment program input 210 | -------------------------------------------------------------------------------- /src/Circuit/Arithmetic.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DeriveAnyClass, DeriveGeneric, LambdaCase, ScopedTypeVariables, 2 | StrictData #-} 3 | 4 | -- | Definition of arithmetic circuits: one with a single 5 | -- multiplication gate with affine inputs and another variant with an 6 | -- arbitrary number of such gates. 7 | module Circuit.Arithmetic 8 | ( Gate (..), 9 | mapVarsGate, 10 | collectInputsGate, 11 | outputWires, 12 | ArithCircuit (..), 13 | fetchVars, 14 | generateRoots, 15 | validArithCircuit, 16 | Wire (..), 17 | evalGate, 18 | evalArithCircuit, 19 | unsplit, 20 | ) 21 | where 22 | 23 | import Circuit.Affine (AffineCircuit(..), collectInputsAffine, 24 | evalAffineCircuit, mapVarsAffine) 25 | import Data.Aeson (FromJSON, ToJSON) 26 | import Data.Field.Galois (PrimeField, fromP) 27 | import Protolude 28 | import Text.PrettyPrint.Leijen.Text as PP (Pretty(..), hsep, list, parens, text, 29 | vcat) 30 | 31 | -- | Wires are can be labeled in the ways given in this data type 32 | data Wire 33 | = InputWire Int 34 | | IntermediateWire Int 35 | | OutputWire Int 36 | deriving (Show, Eq, Ord, Generic, NFData, ToJSON, FromJSON) 37 | 38 | instance Pretty Wire where 39 | pretty (InputWire v) = text "input_" <> pretty v 40 | pretty (IntermediateWire v) = text "imm_" <> pretty v 41 | pretty (OutputWire v) = text "output_" <> pretty v 42 | 43 | -- | An arithmetic circuit with a single multiplication gate. 44 | data Gate i f 45 | = Mul 46 | { mulLeft :: AffineCircuit i f, 47 | mulRight :: AffineCircuit i f, 48 | mulOutput :: i 49 | } 50 | | Equal 51 | { eqInput :: i, 52 | eqMagic :: i, 53 | eqOutput :: i 54 | } 55 | | Split 56 | { splitInput :: i, 57 | splitOutputs :: [i] 58 | } 59 | deriving (Show, Eq, Generic, NFData, FromJSON, ToJSON) 60 | 61 | collectInputsGate :: Ord i => Gate i f -> [i] 62 | collectInputsGate = \case 63 | Mul l r _ -> collectInputsAffine l ++ collectInputsAffine r 64 | _ -> panic "collectInputsGate: only supports mul gates" 65 | 66 | -- | List output wires of a gate 67 | outputWires :: Gate i f -> [i] 68 | outputWires = \case 69 | Mul _ _ out -> [out] 70 | Equal _ _ out -> [out] 71 | Split _ outs -> outs 72 | 73 | instance (Pretty i, Show f) => Pretty (Gate i f) where 74 | pretty (Mul l r o) = 75 | hsep 76 | [ pretty o, 77 | text ":=", 78 | parens (pretty l), 79 | text "*", 80 | parens (pretty r) 81 | ] 82 | pretty (Equal i _ o) = 83 | hsep 84 | [ pretty o, 85 | text ":=", 86 | pretty i, 87 | text "== 0 ? 0 : 1" 88 | ] 89 | pretty (Split inp outputs) = 90 | hsep 91 | [ PP.list (map pretty outputs), 92 | text ":=", 93 | text "split", 94 | pretty inp 95 | ] 96 | 97 | -- | Apply mapping to variable names, i.e. rename variables. (Ideally 98 | -- the mapping is injective.) 99 | mapVarsGate :: (i -> j) -> Gate i f -> Gate j f 100 | mapVarsGate f = \case 101 | Mul l r o -> Mul (mapVarsAffine f l) (mapVarsAffine f r) (f o) 102 | Equal i j o -> Equal (f i) (f j) (f o) 103 | Split i os -> Split (f i) (fmap f os) 104 | 105 | -- | Evaluate a single gate 106 | evalGate :: 107 | (PrimeField f) => 108 | -- | lookup a value at a wire 109 | (i -> vars -> Maybe f) -> 110 | -- | update a value at a wire 111 | (i -> f -> vars -> vars) -> 112 | -- | context before evaluation 113 | vars -> 114 | -- | gate 115 | Gate i f -> 116 | -- | context after evaluation 117 | vars 118 | evalGate lookupVar updateVar vars gate = 119 | case gate of 120 | Mul l r outputWire -> 121 | let lval = evalAffineCircuit lookupVar vars l 122 | rval = evalAffineCircuit lookupVar vars r 123 | res = lval * rval 124 | in updateVar outputWire res vars 125 | Equal i m outputWire -> 126 | case lookupVar i vars of 127 | Nothing -> 128 | panic "evalGate: the impossible happened" 129 | Just inp -> 130 | let res = if inp == 0 then 0 else 1 131 | mid = if inp == 0 then 0 else recip inp 132 | in updateVar outputWire res $ 133 | updateVar m mid vars 134 | Split i os -> 135 | case lookupVar i vars of 136 | Nothing -> 137 | panic "evalGate: the impossible happened" 138 | Just inp -> 139 | let bool2val True = 1 140 | bool2val False = 0 141 | setWire (ix, oldEnv) currentOut = 142 | ( ix + 1, 143 | updateVar currentOut (bool2val $ testBit (fromP inp) ix) oldEnv 144 | ) 145 | in snd . foldl setWire (0, vars) $ os 146 | 147 | -- | A circuit is a list of multiplication gates along with their 148 | -- output wire labels (which can be intermediate or actual outputs). 149 | newtype ArithCircuit f = ArithCircuit [Gate Wire f] 150 | deriving (Eq, Show, Generic, NFData, FromJSON, ToJSON) 151 | 152 | instance Show f => Pretty (ArithCircuit f) where 153 | pretty (ArithCircuit gs) = vcat . map pretty $ gs 154 | 155 | -- | Check whether an arithmetic circuit does not refer to 156 | -- intermediate wires before they are defined and whether output wires 157 | -- are not used as input wires. 158 | validArithCircuit :: 159 | ArithCircuit f -> Bool 160 | validArithCircuit (ArithCircuit gates) = 161 | noRefsToUndefinedWires 162 | where 163 | noRefsToUndefinedWires = 164 | fst $ 165 | foldl 166 | ( \(res, definedWires) gate -> 167 | ( res 168 | && all isNotInput (outputWires gate) 169 | && all (validWire definedWires) (fetchVarsGate gate), 170 | outputWires gate ++ definedWires 171 | ) 172 | ) 173 | (True, []) 174 | gates 175 | isNotInput (InputWire _) = False 176 | isNotInput (OutputWire _) = True 177 | isNotInput (IntermediateWire _) = True 178 | validWire _ (InputWire _) = True 179 | validWire _ (OutputWire _) = False 180 | validWire definedWires i@(IntermediateWire _) = i `elem` definedWires 181 | fetchVarsGate (Mul l r _) = fetchVars l ++ fetchVars r 182 | fetchVarsGate (Equal i _ _) = [i] -- we can ignore the magic 183 | -- variable "m", as it is filled 184 | -- in when evaluating the circuit 185 | fetchVarsGate (Split i _) = [i] 186 | 187 | fetchVars :: AffineCircuit Wire f -> [Wire] 188 | fetchVars (Var i) = [i] 189 | fetchVars (ConstGate _) = [] 190 | fetchVars (ScalarMul _ c) = fetchVars c 191 | fetchVars (Add l r) = fetchVars l ++ fetchVars r 192 | 193 | -- | Generate enough roots for a circuit 194 | generateRoots :: 195 | Applicative m => 196 | m f -> 197 | ArithCircuit f -> 198 | m [[f]] 199 | generateRoots _ (ArithCircuit []) = 200 | pure [] 201 | generateRoots takeRoot (ArithCircuit (gate : gates)) = 202 | case gate of 203 | Mul {} -> 204 | (\r rs -> [r] : rs) 205 | <$> takeRoot 206 | <*> generateRoots takeRoot (ArithCircuit gates) 207 | Equal {} -> 208 | (\r0 r1 rs -> [r0, r1] : rs) 209 | <$> takeRoot 210 | <*> takeRoot 211 | <*> generateRoots takeRoot (ArithCircuit gates) 212 | Split _ outputs -> 213 | (\r0 rOutputs rRest -> (r0 : rOutputs) : rRest) 214 | <$> takeRoot 215 | <*> traverse (const takeRoot) outputs 216 | <*> generateRoots takeRoot (ArithCircuit gates) 217 | 218 | -- | Evaluate an arithmetic circuit on a given environment containing 219 | -- the inputs. Outputs the entire environment (outputs, intermediate 220 | -- values and inputs). 221 | evalArithCircuit :: 222 | forall f vars. 223 | (PrimeField f) => 224 | -- | lookup a value at a wire 225 | (Wire -> vars -> Maybe f) -> 226 | -- | update a value at a wire 227 | (Wire -> f -> vars -> vars) -> 228 | -- | circuit to evaluate 229 | ArithCircuit f -> 230 | -- | input variables 231 | vars -> 232 | -- | input and output variables 233 | vars 234 | evalArithCircuit lookupVar updateVar (ArithCircuit gates) vars = 235 | foldl' (evalGate lookupVar updateVar) vars gates 236 | 237 | -- | Turn a binary expansion back into a single value. 238 | unsplit :: 239 | Num f => 240 | -- | (binary) wires containing a binary expansion, 241 | -- small-endian 242 | [Wire] -> 243 | AffineCircuit Wire f 244 | unsplit = snd . foldl (\(ix, rest) wire -> (ix + (1 :: Integer), Add rest (ScalarMul (2 ^ ix) (Var wire)))) (0, ConstGate 0) 245 | -------------------------------------------------------------------------------- /tex/cc073b29543f2694def1c15e2d40cb07.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /tex/26affbf877c9c00bcf2d4f78f88cedbf.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /tex/dc35769e37858254d0d77fab2d83bcf4.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /tex/2c4cf6568afbabb029a579215dfa6e3e.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /tex/6e1ad13b9c0521871bb453942700c519.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /tex/e5079841837a9e98336245a5ecdb2538.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /tex/4fc182d9ca10181f85444d64991b4f62.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | --------------------------------------------------------------------------------