├── 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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------