Skip to content

Commit 77415e0

Browse files
authored
Merge pull request #12 from l-adic/add-standard-solver
added standard binary solver for circom
2 parents be5e58e + cb35efa commit 77415e0

File tree

20 files changed

+441
-126
lines changed

20 files changed

+441
-126
lines changed

Diff for: .github/workflows/ormolu.yml

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
name: Ormolu CI
2+
3+
on:
4+
push:
5+
branches: [main, master]
6+
pull_request:
7+
branches: [main, master]
8+
9+
jobs:
10+
ormolu:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v4
14+
- uses: haskell-actions/run-ormolu@v15
15+
with:
16+
version: "0.7.2.0"

Diff for: .gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,5 @@
99
dist*/
1010
TAGS
1111
dist-newstyle
12+
inputs.json
13+
circuit-output

Diff for: arithmetic-circuits.cabal

+9-8
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,11 @@ common deps
4444
build-depends:
4545
aeson >=1.4
4646
, base >=4.10 && <5
47+
, binary >=0.8.9 && <0.9
4748
, containers >=0.6.0
4849
, filepath >=1.4.2
4950
, finite-typelits >=0.1.0
5051
, galois-field >=2.0.0
51-
, process
5252
, propagators
5353
, protolude >=0.2
5454
, semirings >=0.7
@@ -103,16 +103,19 @@ library circom-compat
103103
import: deps, extensions, warnings
104104
visibility: public
105105
exposed-modules:
106-
Circuit.Solver.Circom
107106
FNV
108-
R1CS.Circom
107+
Circom.CLI
108+
Circom.Solver
109+
Circom.R1CS
109110

110111
other-modules: Paths_arithmetic_circuits
111112
hs-source-dirs: circom-compat/src
112113
build-depends:
113114
arithmetic-circuits
114-
, binary
115+
, arithmetic-circuits:language
115116
, bytestring
117+
, directory
118+
, optparse-applicative
116119
, vector
117120

118121
ghc-options: -freverse-errors -O2 -Wall
@@ -166,8 +169,7 @@ executable factors
166169
import: warnings, extensions, deps
167170
main-is: Main.hs
168171
build-depends:
169-
binary
170-
, arithmetic-circuits
172+
arithmetic-circuits
171173
, arithmetic-circuits:language
172174
, arithmetic-circuits:circom-compat
173175
, vector
@@ -213,7 +215,7 @@ test-suite circom-compat-tests
213215

214216
other-modules:
215217
Paths_arithmetic_circuits
216-
Test.R1CS.Circom
218+
Test.Circom.R1CS
217219

218220
hs-source-dirs: circom-compat/test
219221

@@ -227,7 +229,6 @@ test-suite circom-compat-tests
227229
, tasty-quickcheck >=0.10
228230
, hspec
229231
, QuickCheck
230-
, binary
231232

232233
build-tool-depends: tasty-discover:tasty-discover >=4.2
233234

Diff for: cabal.project

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ source-repository-package
1313
source-repository-package
1414
type: git
1515
location: https://github.com/l-adic/galois-fields.git
16-
tag: b0867ffdebda5043c80315a51b15e82ed25acba6
16+
tag: 525521de7b985364f7e0c32222fc3b21fea8e530
1717
--sha256: j/zGFd2aeowzJfgCCBmJYmG8mDsfF0irqj/cPOw9ulE=
1818

1919
source-repository-package

Diff for: circom-compat/app/Main.hs

+9-16
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,24 @@
11
module Main where
22

3+
import Circom.CLI (defaultMain)
34
import Circuit
45
import Circuit.Language
5-
import Data.Binary (encodeFile)
66
import Data.Field.Galois (Prime)
7-
import Data.IntMap qualified as IntMap
8-
import Data.IntSet qualified as IntSet
97
import Protolude
10-
import R1CS (Inputs (..), calculateWitness, isValidWitness)
11-
import R1CS.Circom (r1csToCircomR1CS, witnessToCircomWitness)
128

139
main :: IO ()
14-
main = do
15-
let BuilderState {..} = snd $ runCircuitBuilder program
16-
publicInputs = IntMap.fromList $ zip (IntSet.toAscList $ cvPublicInputs bsVars) [6]
17-
privateInputs = IntMap.fromList $ zip (IntSet.toAscList $ cvPrivateInputs bsVars) [2, 3]
18-
inputs = publicInputs <> privateInputs
19-
(r1cs, wtns) = calculateWitness bsVars bsCircuit (Inputs inputs)
20-
unless (isValidWitness wtns r1cs) $ panic "Invalid witness"
21-
encodeFile "circom-compat/examples/factors/circuit.r1cs" $ r1csToCircomR1CS r1cs
22-
encodeFile "circom-compat/examples/factors/witness.wtns" $ witnessToCircomWitness wtns
10+
main = defaultMain "factors" program
2311

2412
type Fr = Prime 21888242871839275222246405745257275088548364400416034343698204186575808495617
2513

2614
program :: ExprM Fr (Var Wire Fr 'TBool)
2715
program = do
28-
n <- var_ <$> fieldInput Public "n"
2916
a <- var_ <$> fieldInput Private "a"
3017
b <- var_ <$> fieldInput Private "b"
31-
boolOutput "out" $ eq_ n (a * b)
18+
n <- var_ <$> fieldInput Public "n"
19+
let cs =
20+
[ neq_ n a,
21+
neq_ n b,
22+
eq_ n (a * b)
23+
]
24+
boolOutput "out" $ unAnd_ $ foldMap And_ cs

Diff for: circom-compat/examples/factors/circuit.bin

546 Bytes
Binary file not shown.

Diff for: circom-compat/examples/factors/circuit.r1cs

856 Bytes
Binary file not shown.

Diff for: circom-compat/examples/factors/witness.wtns

364 Bytes
Binary file not shown.

Diff for: circom-compat/src/Circom/CLI.hs

+175
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
module Circom.CLI (defaultMain) where
2+
3+
import Circom.R1CS (r1csToCircomR1CS)
4+
import Circom.Solver (CircomProgram (..), mkCircomProgram, nativeGenWitness)
5+
import Circuit.Arithmetic (CircuitVars (cvInputsLabels, cvOutputs), InputBindings (labelToVar), restrictVars)
6+
import Circuit.Dataflow qualified as DataFlow
7+
import Circuit.Dot (arithCircuitToDot)
8+
import Circuit.Language.Compile (BuilderState (..), ExprM, runCircuitBuilder)
9+
import Data.Aeson (decodeFileStrict)
10+
import Data.Aeson qualified as A
11+
import Data.Binary (decodeFile, encodeFile)
12+
import Data.Field.Galois (PrimeField (fromP))
13+
import Data.IntSet qualified as IntSet
14+
import Data.Text qualified as Text
15+
import Options.Applicative (CommandFields, Mod, Parser, ParserInfo, command, execParser, fullDesc, header, help, helper, hsubparser, info, long, progDesc, showDefault, strOption, switch, value)
16+
import Protolude
17+
import R1CS (toR1CS)
18+
import System.Directory (createDirectoryIfMissing)
19+
20+
data GlobalOpts = GlobalOpts
21+
{ outputDir :: FilePath,
22+
cmd :: Command
23+
}
24+
25+
optsParser :: Text -> ParserInfo GlobalOpts
26+
optsParser progName =
27+
info
28+
(helper <*> globalOptsParser)
29+
( fullDesc
30+
<> progDesc ("Compiling " <> Text.unpack progName <> " to a zk-SNARK")
31+
<> header ("Compile " <> Text.unpack progName <> " to a system of constraints and solve for a witness")
32+
)
33+
where
34+
globalOptsParser :: Parser GlobalOpts
35+
globalOptsParser =
36+
GlobalOpts
37+
<$> strOption
38+
( long "output-dir"
39+
<> help "output directory"
40+
<> showDefault
41+
<> value "circuit-output"
42+
)
43+
<*> hsubparser (compileCommand <> solveCommand)
44+
45+
compileCommand :: Mod CommandFields Command
46+
compileCommand =
47+
command "compile" (info (Compile <$> compileOptsParser) (progDesc "Compile the program to an r1cs and constraint system"))
48+
49+
solveCommand :: Mod CommandFields Command
50+
solveCommand =
51+
command "solve" (info (Solve <$> solveOptsParser) (progDesc "Generate a witness"))
52+
53+
data Command
54+
= Compile CompileOpts
55+
| Solve SolveOpts
56+
57+
data CompileOpts = CompileOpts
58+
{ coOptimizeOpts :: OptimizeOpts,
59+
coGenInputsTemplate :: Bool,
60+
coGenDotFile :: Bool,
61+
coIncludeJson :: Bool
62+
}
63+
64+
compileOptsParser :: Parser CompileOpts
65+
compileOptsParser =
66+
CompileOpts
67+
<$> optimizeOptsParser
68+
<*> switch
69+
( long "inputs-template"
70+
<> help "generate a template json file for the inputs"
71+
)
72+
<*> switch
73+
( long "dot"
74+
<> help "generate a dot file for the circuit"
75+
)
76+
<*> switch
77+
( long "json"
78+
<> help "also write json versions of artifacts"
79+
)
80+
81+
data OptimizeOpts = OptimizeOpts
82+
{ removeUnreachable :: Bool
83+
}
84+
85+
optimizeOptsParser :: Parser OptimizeOpts
86+
optimizeOptsParser =
87+
OptimizeOpts
88+
<$> switch
89+
( long "remove-unreachable"
90+
<> help "detect and remove variables not contributing to the output"
91+
)
92+
93+
data SolveOpts = SolveOpts
94+
{ soInputsFile :: FilePath,
95+
soIncludeJson :: Bool
96+
}
97+
98+
solveOptsParser :: Parser SolveOpts
99+
solveOptsParser =
100+
SolveOpts
101+
<$> strOption
102+
( long "inputs"
103+
<> help "inputs json file"
104+
<> showDefault
105+
<> value "inputs.json"
106+
)
107+
<*> switch
108+
( long "json"
109+
<> help "also write json versions of artifacts"
110+
)
111+
112+
defaultMain ::
113+
forall f a.
114+
(PrimeField f) =>
115+
Text ->
116+
ExprM f a ->
117+
IO ()
118+
defaultMain progName program = do
119+
opts <- execParser (optsParser progName)
120+
let outDir = outputDir opts
121+
case cmd opts of
122+
Compile compilerOpts -> do
123+
let BuilderState {..} = snd $ runCircuitBuilder program
124+
prog = optimize (coOptimizeOpts compilerOpts) $ mkCircomProgram bsVars bsCircuit
125+
r1cs = r1csToCircomR1CS $ toR1CS (cpVars prog) (cpCircuit prog)
126+
createDirectoryIfMissing True outDir
127+
encodeFile (r1csFilePath outDir) r1cs
128+
encodeFile (binFilePath outDir) prog
129+
when (coGenInputsTemplate compilerOpts) $ do
130+
let inputsTemplate = map (const A.Null) $ labelToVar $ cvInputsLabels $ cpVars prog
131+
A.encodeFile (inputsTemplateFilePath outDir) inputsTemplate
132+
when (coIncludeJson compilerOpts) $ do
133+
A.encodeFile (r1csFilePath outDir <> ".json") (map fromP r1cs)
134+
A.encodeFile (binFilePath outDir <> ".json") (map fromP prog)
135+
when (coGenDotFile compilerOpts) $ do
136+
writeFile (dotFilePath outDir) $ arithCircuitToDot (cpCircuit prog)
137+
Solve solveOpts -> do
138+
inputs <- do
139+
mInputs <- decodeFileStrict (soInputsFile solveOpts)
140+
maybe (panic "Failed to decode inputs") (pure . map (fromInteger @f)) mInputs
141+
circuit <- decodeFile (binFilePath outDir)
142+
let wtns = nativeGenWitness circuit inputs
143+
encodeFile (witnessFilePath outDir) wtns
144+
when (soIncludeJson solveOpts) $ do
145+
A.encodeFile (witnessFilePath outDir <> ".json") (map fromP wtns)
146+
where
147+
baseFilePath :: FilePath -> FilePath
148+
baseFilePath dir = dir <> "/" <> Text.unpack progName
149+
inputsTemplateFilePath dir = dir <> "/" <> "inputs-template.json"
150+
binFilePath dir = baseFilePath dir <> ".bin"
151+
r1csFilePath dir = baseFilePath dir <> ".r1cs"
152+
witnessFilePath dir = baseFilePath dir <> ".wtns"
153+
dotFilePath dir = baseFilePath dir <> ".dot"
154+
155+
optimize ::
156+
forall f.
157+
(Ord f) =>
158+
OptimizeOpts ->
159+
CircomProgram f ->
160+
CircomProgram f
161+
optimize opts =
162+
appEndo . mconcat $
163+
[ performRemoveUnreachable
164+
]
165+
where
166+
performRemoveUnreachable :: Endo (CircomProgram f)
167+
performRemoveUnreachable =
168+
if (removeUnreachable opts)
169+
then Endo $ \prog ->
170+
let outVars :: [Int]
171+
outVars = IntSet.toList $ cvOutputs $ cpVars prog
172+
(newCircuit, usedVars) = DataFlow.removeUnreachable outVars (cpCircuit prog)
173+
newVars = restrictVars (cpVars prog) usedVars
174+
in mkCircomProgram newVars newCircuit
175+
else mempty

0 commit comments

Comments
 (0)