Skip to content

Commit 7a2cd07

Browse files
committed
add ridiculous global encoding options
1 parent 431368f commit 7a2cd07

File tree

4 files changed

+99
-40
lines changed

4 files changed

+99
-40
lines changed

Diff for: .gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ dist-newstyle
1212
inputs.json
1313
circuit-output
1414
factors.*
15+
factors-inputs-template.json

Diff for: arithmetic-circuits.cabal

+1
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ library circom-compat
114114
arithmetic-circuits
115115
, arithmetic-circuits:language
116116
, bytestring
117+
, errors
117118
, optparse-applicative
118119
, vector
119120

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

+97-31
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@ import Circuit.Arithmetic (CircuitVars (..), VarType (..), InputBindings (labelT
66
import Circuit.Dataflow qualified as DataFlow
77
import Circuit.Dot (arithCircuitToDot)
88
import Circuit.Language.Compile (BuilderState (..), ExprM, runCircuitBuilder)
9-
import Data.Aeson (decodeFileStrict)
109
import Data.Aeson qualified as A
10+
import Data.Aeson.Types qualified as A
1111
import Data.Binary (decodeFile, encodeFile)
1212
import Data.Field.Galois (Prime, PrimeField (fromP))
1313
import Data.IntSet qualified as IntSet
1414
import Data.Text qualified as Text
1515
import GHC.TypeNats (SNat, withKnownNat, withSomeSNat)
16-
import Options.Applicative (CommandFields, Mod, Parser, ParserInfo, command, execParser, fullDesc, header, help, helper, hsubparser, info, long, progDesc, showDefault, strOption, switch, value)
16+
import Options.Applicative (CommandFields, Mod, Parser, ParserInfo, command, execParser, fullDesc, header, help, helper, hsubparser, info, long, progDesc, showDefault, strOption, switch, value, option, eitherReader, showDefaultWith)
1717
import Protolude
1818
import R1CS (R1CS, Witness (Witness), isValidWitness, toR1CS)
1919
import Data.Text.Read (decimal, hexadecimal)
@@ -22,9 +22,12 @@ import Data.Map qualified as Map
2222
import Protolude.Unsafe (unsafeHead)
2323
import Data.Maybe (fromJust)
2424
import qualified Data.IntMap as IntMap
25+
import Numeric (showHex)
26+
import Control.Error (hoistEither)
2527

2628
data GlobalOpts = GlobalOpts
2729
{ cmd :: Command
30+
, encoding :: Encoding
2831
}
2932

3033
optsParser :: Text -> ParserInfo GlobalOpts
@@ -40,6 +43,25 @@ optsParser progName =
4043
globalOptsParser =
4144
GlobalOpts
4245
<$> hsubparser (compileCommand <> solveCommand <> verifyCommand)
46+
<*> encodingParser
47+
48+
encodingParser :: Parser Encoding
49+
encodingParser =
50+
let readEncoding = eitherReader $ \case
51+
"hex" -> pure HexString
52+
"decimal-string" -> pure DecString
53+
"decimal" -> pure Dec
54+
_ -> throwError $ "Invalid encoding, expected one of: hex, decimal-string, decimal"
55+
in option readEncoding
56+
( long "encoding"
57+
<> help "encoding for inputs and outputs"
58+
<> showDefaultWith (\case
59+
HexString -> "hex"
60+
DecString -> "decimal-string"
61+
Dec -> "decimal"
62+
)
63+
<> value Dec
64+
)
4365

4466
compileCommand :: Mod CommandFields Command
4567
compileCommand =
@@ -186,9 +208,9 @@ defaultMain progName program = do
186208
let binFilePath = coCircuitBinFile compilerOpts
187209
encodeFile binFilePath prog
188210
when (coGenInputsTemplate compilerOpts) $ do
189-
let inputsTemplate = mkInputsTemplate $ cpVars prog
211+
let inputsTemplate = mkInputsTemplate (encoding opts) (cpVars prog)
190212
inputsTemplateFilePath = Text.unpack progName <> "-inputs-template.json"
191-
A.encodeFile inputsTemplateFilePath inputsTemplate
213+
writeIOVars inputsTemplateFilePath inputsTemplate
192214
when (coIncludeJson compilerOpts) $ do
193215
A.encodeFile (r1csFilePath <> ".json") (map fromP r1cs)
194216
A.encodeFile (binFilePath <> ".json") (map fromP prog)
@@ -197,8 +219,8 @@ defaultMain progName program = do
197219
writeFile dotFilePath $ arithCircuitToDot (cpCircuit prog)
198220
Solve solveOpts -> do
199221
inputs <- do
200-
mInputs <- decodeFileStrict (soInputsFile solveOpts)
201-
maybe (panic "Failed to decode inputs") (pure . map (map (fromInteger @f . unFieldElem))) mInputs
222+
IOVars _ is <- readIOVars (encoding opts) (soInputsFile solveOpts)
223+
pure $ map (map (fromInteger @f . unFieldElem)) is
202224
let binFilePath = soCircuitBinFile solveOpts
203225
circuit <- decodeFile binFilePath
204226
let wtns = nativeGenWitness circuit inputs
@@ -207,8 +229,8 @@ defaultMain progName program = do
207229
when (soIncludeJson solveOpts) $ do
208230
A.encodeFile (wtnsFilePath <> ".json") (map fromP wtns)
209231
when (soShowOutputs solveOpts) $ do
210-
let outputs = mkOutputs (cpVars circuit) (witnessFromCircomWitness wtns)
211-
print $ A.encode (map (map fromP) outputs)
232+
let outputs = mkOutputs (encoding opts) (cpVars circuit) (witnessFromCircomWitness wtns)
233+
print $ A.encode $ encodeIOVars outputs
212234
Verify verifyOpts -> do
213235
let r1csFilePath = voR1CSFile verifyOpts
214236
cr1cs <- decodeR1CSHeaderFromFile r1csFilePath
@@ -246,26 +268,62 @@ optimize opts =
246268
else mempty
247269

248270
--------------------------------------------------------------------------------
271+
-- Programs expecting to interact with Circom via the file system and solver API can
272+
-- be incredibly stupid w.r.t. to accepting / demanding inputs be encoded as strings (either hex or dec)
273+
-- or as numbers.
274+
275+
data Encoding = HexString | DecString | Dec deriving (Eq, Show)
249276

250277
newtype FieldElem = FieldElem {unFieldElem :: Integer} deriving newtype (Eq, Ord, Enum, Num, Real, Integral)
251278

252-
instance A.FromJSON FieldElem where
253-
parseJSON v = case v of
254-
A.String s ->
255-
case hexadecimal s <> decimal s of
256-
Left e -> fail e
257-
Right (a, rest) ->
258-
if Text.null rest
259-
then pure a
260-
else fail $ "FieldElem parser failed to consume all input: " <> Text.unpack rest
261-
_ -> FieldElem <$> A.parseJSON v
262-
instance A.ToJSON FieldElem where
263-
toJSON (FieldElem a) = A.toJSON a
264-
265-
newtype Inputs = Inputs (Map Text (VarType FieldElem)) deriving newtype (A.FromJSON, A.ToJSON)
266-
267-
mkInputsTemplate :: CircuitVars Text -> Inputs
268-
mkInputsTemplate vars =
279+
encodeFieldElem :: Encoding -> FieldElem -> A.Value
280+
encodeFieldElem enc (FieldElem a) = case enc of
281+
HexString -> A.toJSON $ "0x" <> (Text.pack $ showHex a "")
282+
DecString -> A.toJSON $ Text.pack $ show a
283+
Dec -> A.toJSON a
284+
285+
decodeFieldElem :: Encoding -> A.Value -> A.Parser FieldElem
286+
decodeFieldElem enc _v = case enc of
287+
Dec -> FieldElem <$> A.parseJSON _v
288+
DecString -> do
289+
s <- A.parseJSON _v
290+
FieldElem <$> parseDec s
291+
where
292+
parseDec str = case decimal str of
293+
Right (n, "") -> pure n
294+
_ -> fail "FieldElem: expected a decimal string"
295+
HexString -> do
296+
s <- A.parseJSON _v
297+
FieldElem <$> parseHex s
298+
where
299+
parseHex str = case hexadecimal str of
300+
Right (n, "") -> pure n
301+
_ -> fail "FieldElem: expected a hexadecimal string"
302+
303+
encodeVarType :: Encoding -> VarType FieldElem -> A.Value
304+
encodeVarType enc = \case
305+
Simple a -> encodeFieldElem enc a
306+
Array as -> A.toJSON $ map (encodeFieldElem enc) as
307+
308+
decodeVarType :: Encoding -> A.Value -> A.Parser (VarType FieldElem)
309+
decodeVarType enc v = do
310+
vs <- A.parseJSON v
311+
case vs of
312+
A.Array as -> Array <$> traverse (decodeFieldElem enc) (toList as)
313+
_ -> Simple <$> decodeFieldElem enc v
314+
315+
data IOVars = IOVars Encoding (Map Text (VarType FieldElem))
316+
317+
encodeIOVars :: IOVars -> A.Value
318+
encodeIOVars (IOVars enc vs) = A.toJSON $ map (encodeVarType enc) vs
319+
320+
decodeIOVars :: Encoding -> A.Value -> A.Parser IOVars
321+
decodeIOVars enc v = do
322+
kvs <- A.parseJSON v
323+
IOVars enc <$> traverse (decodeVarType enc) kvs
324+
325+
mkInputsTemplate :: Encoding -> CircuitVars Text -> IOVars
326+
mkInputsTemplate enc vars =
269327
let inputsOnly = cvInputsLabels $ restrictVars vars (cvPrivateInputs vars `IntSet.union` cvPublicInputs vars)
270328
vs =
271329
map (\a -> (fst $ unsafeHead a, length a)) $
@@ -276,10 +334,10 @@ mkInputsTemplate vars =
276334
if len > 1
277335
then (label, Array (replicate len 0))
278336
else (label, Simple 0)
279-
in Inputs $ Map.fromList $ map f vs
337+
in IOVars enc $ Map.fromList $ map f vs
280338

281-
mkOutputs :: CircuitVars Text -> Witness f -> Map Text (VarType f)
282-
mkOutputs vars (Witness w) =
339+
mkOutputs :: PrimeField f => Encoding -> CircuitVars Text -> Witness f -> IOVars
340+
mkOutputs enc vars (Witness w) =
283341
let vs :: [[((Text,Int), Int)]]
284342
vs = groupBy (\a b -> fst (fst a) == fst (fst b)) $
285343
Map.toList $
@@ -289,12 +347,20 @@ mkOutputs vars (Witness w) =
289347
f = \case
290348
[((label, _), v)] ->
291349
let val = fromJust $ IntMap.lookup v w
292-
in (label, Simple val)
350+
in (label, Simple . FieldElem . fromP $ val)
293351
as@( ((l, _), _) : _ ) ->
294352
( l
295353
, Array $ fromJust $ for as $ \(_, i) ->
296-
IntMap.lookup i w
354+
FieldElem . fromP <$> IntMap.lookup i w
297355

298356
)
299357
_ -> panic "impossible: groupBy lists are non empty"
300-
in Map.fromList $ map f vs
358+
in IOVars enc (Map.fromList $ map f vs)
359+
360+
writeIOVars :: FilePath -> IOVars -> IO ()
361+
writeIOVars fp (IOVars enc vs) = A.encodeFile fp (encodeIOVars (IOVars enc vs))
362+
363+
readIOVars :: Encoding -> FilePath -> IO IOVars
364+
readIOVars enc fp = map (either (panic . Text.pack) identity) $ runExceptT $ do
365+
contents <- ExceptT $ A.eitherDecodeFileStrict fp
366+
hoistEither $ A.parseEither (decodeIOVars enc) contents

Diff for: circuit/src/Circuit/Arithmetic.hs

-9
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ import Text.PrettyPrint.Leijen.Text as PP
5050
vcat,
5151
(<+>),
5252
)
53-
import qualified Data.Aeson as A
5453

5554
data InputType = Public | Private deriving (Show, Eq, Ord, Generic, NFData)
5655

@@ -390,14 +389,6 @@ instance Functor VarType where
390389
fmap f (Simple a) = Simple (f a)
391390
fmap f (Array as) = Array (map f as)
392391

393-
instance A.FromJSON f => A.FromJSON (VarType f) where
394-
parseJSON (A.Array as) = Array . toList <$> traverse A.parseJSON as
395-
parseJSON a = Simple <$> A.parseJSON a
396-
397-
instance A.ToJSON f => A.ToJSON (VarType f) where
398-
toJSON (Simple a) = A.toJSON a
399-
toJSON (Array as) = A.toJSON as
400-
401392
assignInputs :: forall label f. (Ord label) => CircuitVars label -> Map label (VarType f) -> IntMap f
402393
assignInputs CircuitVars {..} inputs =
403394
let is :: Map (label, Int) f

0 commit comments

Comments
 (0)