Skip to content

Commit b60e3f4

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

File tree

4 files changed

+102
-40
lines changed

4 files changed

+102
-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

+100-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,65 @@ 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 = do
287+
v <- A.parseJSON _v
288+
case v of
289+
A.String s -> case enc of
290+
HexString -> FieldElem <$> parseHex s
291+
DecString -> FieldElem <$> parseDec s
292+
_ -> fail "FieldElem: expected a string"
293+
where
294+
parseHex str = case hexadecimal str of
295+
Right (n, "") -> pure n
296+
_ -> fail "FieldElem: expected a hexadecimal string"
297+
parseDec str = case decimal str of
298+
Right (n, "") -> pure n
299+
_ -> fail "FieldElem: expected a decimal string"
300+
A.Number n ->
301+
case enc of
302+
Dec -> pure $ FieldElem $ floor n
303+
_ -> fail "FieldElem: expected a number"
304+
_ -> fail "FieldElem: expected a string or a number"
305+
306+
encodeVarType :: Encoding -> VarType FieldElem -> A.Value
307+
encodeVarType enc = \case
308+
Simple a -> encodeFieldElem enc a
309+
Array as -> A.toJSON $ map (encodeFieldElem enc) as
310+
311+
decodeVarType :: Encoding -> A.Value -> A.Parser (VarType FieldElem)
312+
decodeVarType enc v = do
313+
vs <- A.parseJSON v
314+
case vs of
315+
A.Array as -> Array <$> traverse (decodeFieldElem enc) (toList as)
316+
_ -> Simple <$> decodeFieldElem enc v
317+
318+
data IOVars = IOVars Encoding (Map Text (VarType FieldElem))
319+
320+
encodeIOVars :: IOVars -> A.Value
321+
encodeIOVars (IOVars enc vs) = A.toJSON $ map (encodeVarType enc) vs
322+
323+
decodeIOVars :: Encoding -> A.Value -> A.Parser IOVars
324+
decodeIOVars enc v = do
325+
kvs <- A.parseJSON v
326+
IOVars enc <$> traverse (decodeVarType enc) kvs
327+
328+
mkInputsTemplate :: Encoding -> CircuitVars Text -> IOVars
329+
mkInputsTemplate enc vars =
269330
let inputsOnly = cvInputsLabels $ restrictVars vars (cvPrivateInputs vars `IntSet.union` cvPublicInputs vars)
270331
vs =
271332
map (\a -> (fst $ unsafeHead a, length a)) $
@@ -276,10 +337,10 @@ mkInputsTemplate vars =
276337
if len > 1
277338
then (label, Array (replicate len 0))
278339
else (label, Simple 0)
279-
in Inputs $ Map.fromList $ map f vs
340+
in IOVars enc $ Map.fromList $ map f vs
280341

281-
mkOutputs :: CircuitVars Text -> Witness f -> Map Text (VarType f)
282-
mkOutputs vars (Witness w) =
342+
mkOutputs :: PrimeField f => Encoding -> CircuitVars Text -> Witness f -> IOVars
343+
mkOutputs enc vars (Witness w) =
283344
let vs :: [[((Text,Int), Int)]]
284345
vs = groupBy (\a b -> fst (fst a) == fst (fst b)) $
285346
Map.toList $
@@ -289,12 +350,20 @@ mkOutputs vars (Witness w) =
289350
f = \case
290351
[((label, _), v)] ->
291352
let val = fromJust $ IntMap.lookup v w
292-
in (label, Simple val)
353+
in (label, Simple . FieldElem . fromP $ val)
293354
as@( ((l, _), _) : _ ) ->
294355
( l
295356
, Array $ fromJust $ for as $ \(_, i) ->
296-
IntMap.lookup i w
357+
FieldElem . fromP <$> IntMap.lookup i w
297358

298359
)
299360
_ -> panic "impossible: groupBy lists are non empty"
300-
in Map.fromList $ map f vs
361+
in IOVars enc (Map.fromList $ map f vs)
362+
363+
writeIOVars :: FilePath -> IOVars -> IO ()
364+
writeIOVars fp (IOVars enc vs) = A.encodeFile fp (encodeIOVars (IOVars enc vs))
365+
366+
readIOVars :: Encoding -> FilePath -> IO IOVars
367+
readIOVars enc fp = map (either (panic . Text.pack) identity) $ runExceptT $ do
368+
contents <- ExceptT $ A.eitherDecodeFileStrict fp
369+
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)