From f19f908d0a40d44ff24ab445038c365508d47807 Mon Sep 17 00:00:00 2001 From: Balazs Komuves Date: Mon, 10 Mar 2025 20:55:44 +0100 Subject: [PATCH] implement quadratic field extension (unfortunately, for some reasons, the test framework doesn't work for the more interesting extension field operations...??) --- circuit/field_params.circom | 8 +- circuit/goldilocks.circom | 11 ++- circuit/goldilocks_ext.circom | 140 ++++++++++++++++++++++++++++++++ circuit/run.sh | 6 +- circuit/test_wrapper.circom | 13 +++ circuit/test_wrapper_ext.circom | 104 ++++++++++++++++++++++++ circuit/testmain.circom | 8 +- tests/Main.hs | 42 +++++++--- tests/Semantics.hs | 126 +++++++++++++++++++++++++++- tests/TestGoldilocks.hs | 5 ++ tests/TestGoldilocksExt.hs | 118 +++++++++++++++++++++++++++ 11 files changed, 562 insertions(+), 19 deletions(-) create mode 100644 circuit/goldilocks_ext.circom create mode 100644 circuit/test_wrapper_ext.circom create mode 100644 tests/TestGoldilocksExt.hs diff --git a/circuit/field_params.circom b/circuit/field_params.circom index 4cf5a1b..2fd1819 100644 --- a/circuit/field_params.circom +++ b/circuit/field_params.circom @@ -18,11 +18,11 @@ pragma circom 2.2.0; //------------------------------------------------------------------------------ -function SolinasExpoBig() { return 64; } -function SolinasExpoSmall() { return 32; } +// function SolinasExpoBig() { return 64; } +// function SolinasExpoSmall() { return 32; } -// function SolinasExpoBig() { return 8; } -// function SolinasExpoSmall() { return 4; } +function SolinasExpoBig() { return 8; } +function SolinasExpoSmall() { return 4; } function FieldPrime() { return (2**SolinasExpoBig() - 2**SolinasExpoSmall() + 1); diff --git a/circuit/goldilocks.circom b/circuit/goldilocks.circom index 8525e0d..37d5299 100644 --- a/circuit/goldilocks.circom +++ b/circuit/goldilocks.circom @@ -191,7 +191,6 @@ template Mul() { C <== ReduceModP( SolinasExpoBig() )( A.val * B.val ); } - // // multiplication of 3 Goldilocks field elements // as this still fits into 192 < 254 bits, we can do it a bit more efficiently @@ -209,6 +208,16 @@ template Mul3() { D <== ReduceModP( 2 * SolinasExpoBig() )( AB * C.val ); } +//-------------------------------------- + +// Squaring (this is more interesting in the extension field case) +// +template Sqr() { + input Goldilocks() A; + output Goldilocks() C; + C <== Mul()( A , A ); +} + //------------------------------------------------------------------------------ // diff --git a/circuit/goldilocks_ext.circom b/circuit/goldilocks_ext.circom new file mode 100644 index 0000000..2e3b31c --- /dev/null +++ b/circuit/goldilocks_ext.circom @@ -0,0 +1,140 @@ + +// +// the quadratic field extension `F[X] / (X^2 - 7)` over Goldilocks +// +// note: `X^2 - 7` is also irreducible over the field of size `2^8 - 2^4 + 1` +// so can use it for testing too. +// + +pragma circom 2.2.0; + +include "goldilocks.circom"; +// include "misc.circom"; + +//------------------------------------------------------------------------------ +// +// the type of quadratic field extension elements. +// + +bus GoldilocksExt() { + Goldilocks() real; + Goldilocks() imag; +} + +template GoldilocksToExt() { + input Goldilocks() inp; + output GoldilocksExt() out; + out.real <== inp; + out.imag.val <== 0; +} + +//------------------------------------------------------------------------------ + +template NegExt() { + input GoldilocksExt() A; + output GoldilocksExt() C; + + C.real <== Neg()( A.real ); + C.imag <== Neg()( A.imag ); +} + +template AddExt() { + input GoldilocksExt() A,B; + output GoldilocksExt() C; + + C.real <== Add()( A.real , B.real ); + C.imag <== Add()( A.imag , B.imag ); +} + +template SubExt() { + input GoldilocksExt() A,B; + output GoldilocksExt() C; + + C.real <== Sub()( A.real , B.real ); + C.imag <== Sub()( A.imag , B.imag ); +} + +//------------------------------------------------------------------------------ + +template SevenTimesProduct() { + input Goldilocks() inp1,inp2; + output Goldilocks() out; + out <== ReduceModP( 3 + SolinasExpoBig() )( 7 * inp1.val * inp2.val ); +} + +template SevenTimesSquare() { + input Goldilocks() inp; + output Goldilocks() out; + out <== ReduceModP( 3 + SolinasExpoBig() )( 7 * inp.val * inp.val ); +} + +//-------------------------------------- + +template MulExt() { + input GoldilocksExt() A,B; + output GoldilocksExt() C; + + Goldilocks() RR, IR, RI, IIx7; + RR <== Mul()( A.real , B.real ); + IR <== Mul()( A.imag , B.real ); + RI <== Mul()( A.real , B.imag ); + IIx7 <== SevenTimesProduct()( A.imag , B.imag ); + + C.real <== Add()( RR , IIx7 ); + C.imag <== Add()( IR , RI ); +} + +template SqrExt() { + input GoldilocksExt() A; + output GoldilocksExt() C; + + Goldilocks() RR, IR, IIx7; + RR <== Sqr()( A.real ); + IR <== Mul()( A.imag , A.real ); + IIx7 <== SevenTimesSquare()( A.imag ); + + C.real <== Add()( RR , IIx7 ); + C.imag <== Add()( IR , IR ); + + // log("\nsqrExt"); + // log( "A = (", A.real.val, " , ", A.imag.val, ")" ); + // log( "C = (", C.real.val, " , ", C.imag.val, ")" ); + // log( "RR = ", RR.val ); + // log( "IR = ", IR.val ); + // log( "IRx7 = ", IIx7.val ); +} + +//------------------------------------------------------------------------------ + +template InvExt() { + input GoldilocksExt() A; + output GoldilocksExt() C; + + Goldilocks() seven, RR, IIx7; + seven.val <== 7; + RR <== Sqr()( A.real ); + IIx7 <== SevenTimesSquare()( A.imag ); + + Goldilocks() denom <== Sub()( RR , IIx7 ); + Goldilocks() mult <== Inv()( denom ); + Goldilocks() minusImag <== Neg()( A.imag ); + + C.real <== Mul()( A.real , mult ); + C.imag <== Mul()( minusImag , mult ); +} + +template DivExt() { + input GoldilocksExt() A,B; + output GoldilocksExt() C; + + GoldilocksExt() invB <== InvExt()( B ); + C <== MulExt()( A , invB ); + + log("\ndivExt"); + log( "A = (", A.real.val, " , ", A.imag.val, ")" ); + log( "B = (", B.real.val, " , ", B.imag.val, ")" ); + log( "C = (", C.real.val, " , ", C.imag.val, ")" ); + +} + +//------------------------------------------------------------------------------ diff --git a/circuit/run.sh b/circuit/run.sh index 1be6662..0d94d3f 100755 --- a/circuit/run.sh +++ b/circuit/run.sh @@ -2,13 +2,15 @@ ORIG=`pwd` MAIN="testmain" -INPUT="dummy.json" +INPUT="input.json" cd build circom ../${MAIN}.circom --r1cs --wasm -echo '{ "dummy": 666 }' >${INPUT} +#echo '{ "dummy": 666 }' >${INPUT} +#echo '{ "A": [66,111] }' >${INPUT} +echo '{ "A": [57,137] , "B": [66,111] }' >${INPUT} cd ${MAIN}_js diff --git a/circuit/test_wrapper.circom b/circuit/test_wrapper.circom index fb3fce0..a935848 100644 --- a/circuit/test_wrapper.circom +++ b/circuit/test_wrapper.circom @@ -65,6 +65,17 @@ template SubWrapper() { //------------------------------------------------------------------------------ +template SqrWrapper() { + signal input A; + signal output C; + + Goldilocks() A1; A1.val <== A; + Goldilocks() C1; + + C1 <== Sqr()( A1 ); + C1.val ==> C; +} + template MulWrapper() { signal input A,B; signal output C; @@ -77,6 +88,8 @@ template MulWrapper() { C1.val ==> C; } +//------------------------------------------------------------------------------ + template InvWrapper() { signal input A; signal output C; diff --git a/circuit/test_wrapper_ext.circom b/circuit/test_wrapper_ext.circom new file mode 100644 index 0000000..2e2d8a3 --- /dev/null +++ b/circuit/test_wrapper_ext.circom @@ -0,0 +1,104 @@ + +// wrappers around the Goldilocks templates, so that input and output are classic +// signals, not Bus-es. The testing framework does not handle Bus inputs/outputs yet + +pragma circom 2.2.0; + +include "goldilocks.circom"; +include "goldilocks_ext.circom"; + +//------------------------------------------------------------------------------ + +template NegExtWrapper() { + signal input A[2]; + signal output C[2]; + + GoldilocksExt() A1; A1.real.val <== A[0]; A1.imag.val <== A[1]; + + GoldilocksExt() C1 <== NegExt()( A1 ); + + C1.real.val ==> C[0]; + C1.imag.val ==> C[1]; +} + +template AddExtWrapper() { + signal input A[2],B[2]; + signal output C[2]; + + GoldilocksExt() A1; A1.real.val <== A[0]; A1.imag.val <== A[1]; + GoldilocksExt() B1; B1.real.val <== B[0]; B1.imag.val <== B[1]; + + GoldilocksExt() C1 <== AddExt()( A1 , B1 ); + + C1.real.val ==> C[0]; + C1.imag.val ==> C[1]; +} + +template SubExtWrapper() { + signal input A[2],B[2]; + signal output C[2]; + + GoldilocksExt() A1; A1.real.val <== A[0]; A1.imag.val <== A[1]; + GoldilocksExt() B1; B1.real.val <== B[0]; B1.imag.val <== B[1]; + + GoldilocksExt() C1 <== SubExt()( A1 , B1 ); + + C1.real.val ==> C[0]; + C1.imag.val ==> C[1]; +} + +//------------------------------------------------------------------------------ + +template SqrExtWrapper() { + signal input A[2]; + signal output C[2]; + + GoldilocksExt() A1; A1.real.val <== A[0]; A1.imag.val <== A[1]; + + GoldilocksExt() C1 <== SqrExt()( A1 ); + + C1.real.val ==> C[0]; + C1.imag.val ==> C[1]; +} + +template MulExtWrapper() { + signal input A[2],B[2]; + signal output C[2]; + + GoldilocksExt() A1; A1.real.val <== A[0]; A1.imag.val <== A[1]; + GoldilocksExt() B1; B1.real.val <== B[0]; B1.imag.val <== B[1]; + + GoldilocksExt() C1 <== MulExt()( A1 , B1 ); + + C1.real.val ==> C[0]; + C1.imag.val ==> C[1]; +} + +//------------------------------------------------------------------------------ + +template InvExtWrapper() { + signal input A[2]; + signal output C[2]; + + GoldilocksExt() A1; A1.real.val <== A[0]; A1.imag.val <== A[1]; + + GoldilocksExt() C1 <== InvExt()( A1 ); + + C1.real.val ==> C[0]; + C1.imag.val ==> C[1]; +} + +template DivExtWrapper() { + signal input A[2],B[2]; + signal output C[2]; + + GoldilocksExt() A1; A1.real.val <== A[0]; A1.imag.val <== A[1]; + GoldilocksExt() B1; B1.real.val <== B[0]; B1.imag.val <== B[1]; + + GoldilocksExt() C1 <== DivExt()( A1 , B1 ); + + C1.real.val ==> C[0]; + C1.imag.val ==> C[1]; +} + +//------------------------------------------------------------------------------ diff --git a/circuit/testmain.circom b/circuit/testmain.circom index e969d27..ade396b 100644 --- a/circuit/testmain.circom +++ b/circuit/testmain.circom @@ -2,9 +2,15 @@ pragma circom 2.2.0; include "test_wrapper.circom"; +include "test_wrapper_ext.circom"; + include "poseidon.circom"; // component main { public [A,B] } = AddWrapper(); // component main { public [A] } = InvWrapper(); -component main { public [dummy] } = CheckPermutationKAT(); +// component main { public [dummy] } = CheckPermutationKAT(); + +// component main { public [A,B] } = MulExtWrapper(); +// component main { public [A] } = SqrExtWrapper(); +component main { public [A,B] } = DivExtWrapper(); diff --git a/tests/Main.hs b/tests/Main.hs index 506aba0..eb2f8a1 100644 --- a/tests/Main.hs +++ b/tests/Main.hs @@ -8,7 +8,8 @@ module Main where import R1CS -import TestGoldilocks +import qualified TestGoldilocks as Gld +import qualified TestGoldilocksExt as Ext -------------------------------------------------------------------------------- @@ -21,18 +22,39 @@ testGoldilocks' fld verbosity = runWithField fld $ \pxy -> do let runSpec what = testSemantics pxy what verbosity let runSpecMany what = testSemanticsMany pxy what verbosity - runSpec specIsZero - runSpec specToGoldi - runSpec $ specUnary Neg semantics_neg - runSpec $ specBinary Add semantics_add - runSpec $ specBinary Sub semantics_sub - runSpec $ specUnary Inv semantics_inv + runSpec Gld.specIsZero + runSpec Gld.specToGoldi + runSpec $ Gld.specUnary Gld.Neg Gld.semantics_neg + runSpec $ Gld.specBinary Gld.Add Gld.semantics_add + runSpec $ Gld.specBinary Gld.Sub Gld.semantics_sub + runSpec $ Gld.specUnary Gld.Sqr Gld.semantics_sqr + runSpec $ Gld.specUnary Gld.Inv Gld.semantics_inv -- these are very slow so we don't do exhaustive testing - runSpec $ specBinarySmall Mul semantics_mul - runSpec $ specBinarySmall Div semantics_div + runSpec $ Gld.specBinarySmall Gld.Mul Gld.semantics_mul + runSpec $ Gld.specBinarySmall Gld.Div Gld.semantics_div + +-------------------------------------------------------------------------------- + +testGoldilocksExt :: IO () +testGoldilocksExt = testGoldilocksExt' Field24 Silent + +testGoldilocksExt' :: FieldChoice -> Verbosity -> IO () +testGoldilocksExt' fld verbosity = runWithField fld $ \pxy -> do + + let runSpec what = testSemantics pxy what verbosity + let runSpecMany what = testSemanticsMany pxy what verbosity + + runSpec $ Ext.specUnary Ext.Neg Ext.semantics_neg + runSpec $ Ext.specBinary Ext.Add Ext.semantics_add + runSpec $ Ext.specBinary Ext.Sub Ext.semantics_sub +-- runSpec $ Ext.specUnary Ext.Sqr Ext.semantics_sqr +-- runSpec $ Ext.specBinary Ext.Mul Ext.semantics_mul +-- runSpec $ Ext.specUnary Ext.Inv Ext.semantics_inv +-- runSpec $ Ext.specBinary Ext.Div Ext.semantics_div -------------------------------------------------------------------------------- main = do - testGoldilocks \ No newline at end of file + testGoldilocks + testGoldilocksExt diff --git a/tests/Semantics.hs b/tests/Semantics.hs index 743cd16..3a9cc47 100644 --- a/tests/Semantics.hs +++ b/tests/Semantics.hs @@ -1,13 +1,17 @@ +{-# LANGUAGE BlockArguments #-} module Semantics where -------------------------------------------------------------------------------- import Prelude hiding (div) +import Control.Monad +import System.Random -------------------------------------------------------------------------------- -type F = Int +type F = Int +type FExt = (F,F) fieldPrime :: Int fieldPrime = 2^8 - 2^4 + 1 @@ -54,3 +58,123 @@ div :: F -> F -> F div x y = mul x (inv y) -------------------------------------------------------------------------------- + +negExt :: FExt -> FExt +negExt (r,i) = (neg r, neg i) + +addExt :: FExt -> FExt -> FExt +addExt (r1,i1) (r2,i2) = (add r1 r2, add i1 i2) + +subExt :: FExt -> FExt -> FExt +subExt (r1,i1) (r2,i2) = (sub r1 r2, sub i1 i2) + +mulExt :: FExt -> FExt -> FExt +mulExt (r1,i1) (r2,i2) = (r3,i3) where + r3 = (mul r1 r2) `add` (mul3 7 i1 i2) + i3 = (mul r1 i2) `add` (mul i1 r2) + +sqrExt :: FExt -> FExt +sqrExt x = mulExt x x + +powExt :: FExt -> Int -> FExt +powExt x0 expo + | expo < 0 = error "pow: negative exponent" + | expo == 0 = (1,0) + | otherwise = go (1,0) x0 expo + where + go acc s 0 = acc + go acc s e = case divMod e 2 of + (e', 0) -> go acc (sqrExt s) e' + (e' ,1) -> go (mulExt acc s) (sqrExt s) e' + +invExt :: FExt -> FExt +invExt (r,i) = (r `mul` z , neg i `mul` z) where + denom = (sqr r) `sub` (mul3 7 i i) + z = inv denom + +divExt :: FExt -> FExt -> FExt +divExt x y = mulExt x (invExt y) + +-------------------------------------------------------------------------------- +-- some quick & dirty testing + +data Prop a + = Prop1 String (a -> Bool) + | Prop2 String (a -> a -> Bool) + | Prop3 String (a -> a -> a -> Bool) + +propName :: Prop a -> String +propName prop = case prop of + Prop1 name _ -> name + Prop2 name _ -> name + Prop3 name _ -> name + +some_properties :: [Prop F] +some_properties = + [ Prop2 "add-sub" \x y -> add (sub x y) y == x + , Prop2 "add-neg" \x y -> sub x y == add x (neg y) + , Prop1 "inv-mul" \x -> x == 0 || inv x `mul` x == 1 + , Prop1 "inv-pow" \x -> inv x == pow x (fieldPrime - 2) + , Prop2 "mul-div" \x y -> y == 0 || mul (div x y) y == x + , Prop3 "mul-add" \x y z -> mul (add x y) z == mul x z `add` mul y z + , Prop1 "mul-pow" \x -> mul (mul x x) x == pow x 3 + ] + +some_ext_properties :: [Prop FExt] +some_ext_properties = + [ Prop2 "add-sub" \x y -> addExt (subExt x y) y == x + , Prop2 "add-neg" \x y -> subExt x y == addExt x (negExt y) + , Prop1 "inv-mul" \x -> x == (0,0) || invExt x `mulExt` x == (1,0) + , Prop1 "inv-pow" \x -> invExt x == powExt x (fieldPrime^2 - 2) + , Prop2 "mul-div" \x y -> y == (0,0) || mulExt (divExt x y) y == x + , Prop3 "mul-add" \x y z -> mulExt (addExt x y) z == mulExt x z `addExt` mulExt y z + , Prop1 "mul-pow" \x -> mulExt (mulExt x x) x == powExt x 3 + ] + +-------------------------------------------------------------------------------- + +runTests :: IO () +runTests = do + let n = 1000 + runTestsBase n + runTestsExt n + +runTestsBase :: Int -> IO () +runTestsBase n = do + putStrLn $ "\nbase field properties" + forM_ some_properties $ \prop -> do + oks <- replicateM n (runPropF prop) + let good = length (filter id oks) + let bad = if good < n then " - FAILED!" else " - OK." + putStrLn $ " - " ++ propName prop ++ ": " ++ show good ++ " / " ++ show n ++ " passed. " ++ bad + +runTestsExt :: Int -> IO () +runTestsExt n = do + putStrLn $ "\nextension field properties" + forM_ some_ext_properties $ \prop -> do + oks <- replicateM n (runPropFExt prop) + let good = length (filter id oks) + let bad = if good < n then " - FAILED!" else " - OK." + putStrLn $ " - " ++ propName prop ++ ": " ++ show good ++ " / " ++ show n ++ " passed. " ++ bad + +---------------------------------------- + +rndF :: IO F +rndF = randomRIO (0,fieldPrime-1) + +rndFExt :: IO FExt +rndFExt = (,) <$> rndF <*> rndF + +runPropF :: Prop F -> IO Bool +runPropF prop = case prop of + Prop1 _ f1 -> f1 <$> rndF + Prop2 _ f2 -> f2 <$> rndF <*> rndF + Prop3 _ f3 -> f3 <$> rndF <*> rndF <*> rndF + +runPropFExt :: Prop FExt -> IO Bool +runPropFExt prop = case prop of + Prop1 _ f1 -> f1 <$> rndFExt + Prop2 _ f2 -> f2 <$> rndFExt <*> rndFExt + Prop3 _ f3 -> f3 <$> rndFExt <*> rndFExt <*> rndFExt + +-------------------------------------------------------------------------------- diff --git a/tests/TestGoldilocks.hs b/tests/TestGoldilocks.hs index 5fc95f5..666fd51 100644 --- a/tests/TestGoldilocks.hs +++ b/tests/TestGoldilocks.hs @@ -18,6 +18,7 @@ data Op = Neg | Add | Sub + | Sqr | Mul | Inv | Div @@ -34,6 +35,7 @@ mainComponent op = Neg -> unary "Neg" Add -> binary "Add" Sub -> binary "Sub" + Sqr -> unary "Sqr" Mul -> binary "Mul" Inv -> unary "Inv" Div -> binary "Div" @@ -81,6 +83,9 @@ semantics_add (x,y) = Expecting $ Semantics.add x y semantics_sub :: (F,F) -> Expected F semantics_sub (x,y) = Expecting $ Semantics.sub x y +semantics_sqr :: F -> Expected F +semantics_sqr x = Expecting $ Semantics.sqr x + semantics_mul :: (F,F) -> Expected F semantics_mul (x,y) = Expecting $ Semantics.mul x y diff --git a/tests/TestGoldilocksExt.hs b/tests/TestGoldilocksExt.hs new file mode 100644 index 0000000..3c98fd8 --- /dev/null +++ b/tests/TestGoldilocksExt.hs @@ -0,0 +1,118 @@ + + +module TestGoldilocksExt where + +-------------------------------------------------------------------------------- + +import Control.Monad +import System.IO.Unsafe + +import Semantics +import Common + +-------------------------------------------------------------------------------- +-- global parameters + +circomFile :: FilePath +circomFile = circuitSourceDir "test_wrapper_ext.circom" + +data Op + = Neg + | Add + | Sub + | Sqr + | Mul + | Inv + | Div + deriving (Eq,Show,Bounded,Enum) + +enumerateOps :: [Op] +enumerateOps = enumFromTo minBound maxBound + +---------------------------------------- + +mainComponent :: Op -> MainComponent +mainComponent op = + case op of + Neg -> unary "NegExt" + Add -> binary "AddExt" + Sub -> binary "SubExt" + Sqr -> unary "SqrExt" + Mul -> binary "MulExt" + Inv -> unary "InvExt" + Div -> binary "DivExt" + where + + unary name = MainComponent + { _templateName = name ++ "Wrapper" + , _templateParams = [] + , _publicInputs = ["A"] + } + + binary name = MainComponent + { _templateName = name ++ "Wrapper" + , _templateParams = [] + , _publicInputs = ["A","B"] + } + +-------------------------------------------------------------------------------- +-- test cases and expected semantics + +type TestCase1 = (Int,Int) +type TestCase2 = ((Int,Int),(Int,Int)) + +type Output = (Int,Int) + +nNestCases = 10 + +randomTestCasesUnary :: [TestCase1] +randomTestCasesUnary = unsafePerformIO $ replicateM nNestCases rndFExt + +randomTestCasesBinary :: [TestCase2] +randomTestCasesBinary = unsafePerformIO $ replicateM nNestCases $ do { x <- rndFExt ; y <- rndFExt ; return (x,y) } + +---------------------------------------- + +semantics_neg :: FExt -> Expected FExt +semantics_neg x = Expecting $ Semantics.negExt x + +semantics_add :: (FExt,FExt) -> Expected FExt +semantics_add (x,y) = Expecting $ Semantics.addExt x y + +semantics_sub :: (FExt,FExt) -> Expected FExt +semantics_sub (x,y) = Expecting $ Semantics.subExt x y + +semantics_sqr :: FExt -> Expected FExt +semantics_sqr x = Expecting $ Semantics.sqrExt x + +semantics_mul :: (FExt,FExt) -> Expected FExt +semantics_mul (x,y) = Expecting $ Semantics.mulExt x y + +semantics_inv :: FExt -> Expected FExt +semantics_inv x = Expecting $ Semantics.invExt x + +semantics_div :: (FExt,FExt) -> Expected FExt +semantics_div (x,y) = Expecting $ Semantics.divExt x y + +-------------------------------------------------------------------------------- +-- inputs and outputs + +inputsA :: TestCase1 -> Inputs Name Integer +inputsA a = Inputs $ toMapping "A" a + +inputsAB :: TestCase2 -> Inputs Name Integer +inputsAB (a,b) = Inputs $ toMapping "A" a + <> toMapping "B" b + +outputsC :: Output -> Outputs Name Integer +outputsC y = Outputs $ toMapping "C" y + +-------------------------------------------------------------------------------- + +specUnary :: Op -> (FExt -> Expected FExt) -> TestSpec TestCase1 Output +specUnary op semantics = TestSpec circomFile (mainComponent op) inputsA outputsC semantics randomTestCasesUnary + +specBinary :: Op -> ((FExt,FExt) -> Expected FExt) -> TestSpec TestCase2 Output +specBinary op semantics = TestSpec circomFile (mainComponent op) inputsAB outputsC semantics randomTestCasesBinary + +--------------------------------------------------------------------------------