packages feed

arithmetic-circuits-0.2.0: test/Test/Circuit/Arithmetic.hs

{-# LANGUAGE DeriveAnyClass, DeriveGeneric, TupleSections #-}

module Test.Circuit.Arithmetic where

import           Circuit.Affine
import           Circuit.Arithmetic
import           Data.Curve.Weierstrass.BN254 (Fr)
import           Data.Map                     (Map)
import qualified Data.Map                     as Map
import           Data.Pairing.BN254           (getRootOfUnity)
import           Fresh
import           Protolude
import           QAP
import           Test.Tasty.HUnit
import           Test.Tasty.QuickCheck

-------------------------------------------------------------------------------
-- Test values
-------------------------------------------------------------------------------

testEqualCircuit :: ArithCircuit Fr
testEqualCircuit = ArithCircuit [Equal (InputWire 0) (IntermediateWire 0) (OutputWire 0)]

testInputMap :: Fr -> Map Int Fr
testInputMap = Map.singleton 0

testSplitUnsplitCircuit :: Int -> ArithCircuit Fr
testSplitUnsplitCircuit nbits =
  ArithCircuit
    [ Split (InputWire 0) midWires,
      Mul (ConstGate 1) (unsplit midWires) (OutputWire 0)
    ]
  where
    midWires = fmap IntermediateWire [0 .. nbits - 1]

-------------------------------------------------------------------------------
-- Generators
-------------------------------------------------------------------------------

arbVars :: Int -> [Int] -> [Gen (AffineCircuit Wire f)]
arbVars numInps mids =
  varInps numInps ++ varMids mids
  where
    varInps size
      | size <= 0 = []
      | otherwise = [Var . InputWire <$> choose (0, numInps - 1)]
    varMids [] = []
    varMids ms@(_ : _) = [Var . IntermediateWire <$> elements ms]

arbAffineCircuitWithMids ::
  Arbitrary f =>
  Int ->
  [Int] ->
  Int ->
  Gen (AffineCircuit Wire f)
arbAffineCircuitWithMids numInps mids size
  | size <= 0 =
    oneof $ [ConstGate <$> arbitrary] ++ arbVars numInps mids
  | size > 0 =
    oneof
      [ ScalarMul <$> arbitrary <*> arbAffineCircuitWithMids numInps mids (size - 1),
        Add <$> arbAffineCircuitWithMids numInps mids (size - 1)
          <*> arbAffineCircuitWithMids numInps mids (size - 1)
      ]

arbInputVector :: (Arbitrary f) => Int -> Gen (Map Int f)
arbInputVector numVars = Map.fromList . zip [0 ..] <$> vector numVars

arbArithCircuit ::
  Arbitrary f =>
  -- | distribution of frequency of mul/equal/split
  -- gates, respectively
  (Int, Int, Int) ->
  Int ->
  Int ->
  Gen (ArithCircuit f)
arbArithCircuit (distMul, distEqual, distSplit) numInps size
  | size <= 0 =
    pure $ ArithCircuit []
  | size > 0 =
    do
      ArithCircuit gates <- arbArithCircuit (distMul, distEqual, distSplit) numInps (size - 1)
      let mids = [i | IntermediateWire i <- concatMap outputWires gates]
      frequency . catMaybes $
        [ (distMul,) <$> mulGate gates mids,
          (distEqual,) <$> equalGate gates mids,
          (distSplit,) <$> splitGate gates mids
        ]
  where
    mulGate gates mids =
      Just $ do
        lhs <- arbAffineCircuitWithMids numInps mids 1
        rhs <- arbAffineCircuitWithMids numInps mids 1
        let outWire = case mids of
              [] -> 0
              ms@(_ : _) -> maximum ms + 1
            gate = Mul lhs rhs (IntermediateWire outWire)
        pure . ArithCircuit $ gates ++ [gate]
    equalGate _ [] =
      Nothing
    equalGate gates mids@(_ : _) =
      Just $ do
        inp <- elements mids
        let outWire =
              case mids of
                [] -> 0
                ms@(_ : _) -> maximum ms + 1
            gate =
              Equal
                (IntermediateWire inp)
                (IntermediateWire outWire)
                (IntermediateWire $ outWire + 1)
        pure . ArithCircuit $ gates ++ [gate]
    splitGate _ [] =
      Nothing
    splitGate gates mids@(_ : _) =
      Just $ do
        inp <- IntermediateWire <$> elements mids
        let firstOutWire =
              case mids of
                [] -> 0
                ms@(_ : _) -> maximum ms + 1
            nbits = 256
            outWires = fmap IntermediateWire [firstOutWire .. firstOutWire + nbits - 1]
            gate = Split inp outWires
        pure . ArithCircuit $ gates ++ [gate]

-- | The input vector has to have the correct length, so we want to
-- generate the program and the test input simultaneously.
data ArithCircuitWithInputs f = ArithCircuitWithInputs (ArithCircuit f) [Map Int f]
  deriving (Show, Generic, NFData)

instance (Arbitrary f, Num f) => Arbitrary (ArithCircuitWithInputs f) where
  arbitrary = do
    numVars <- abs <$> arbitrary `suchThat` (> 0)
    program <- sized (arbArithCircuit (50, 10, 1) numVars)
    inputs <- vectorOf 5 $ arbInputVector numVars
    pure $ ArithCircuitWithInputs program inputs

data ArithCircuitWithInput f = ArithCircuitWithInput (ArithCircuit f) (Map Int f)
  deriving (Show, Generic, NFData)

instance (Arbitrary f, Num f) => Arbitrary (ArithCircuitWithInput f) where
  arbitrary = do
    numVars <- abs <$> arbitrary `suchThat` (> 0)
    program <- sized (arbArithCircuit (50, 10, 1) numVars)
    input <- arbInputVector numVars
    pure $ ArithCircuitWithInput program input

-------------------------------------------------------------------------------
-- Tests
-------------------------------------------------------------------------------

unit_eqGate ::
  Assertion
unit_eqGate =
  do
    testEvalWith 0 @?= Just 0
    testEvalWith 1 @?= Just 1
    testEvalWith 2 @?= Just 1
    testEvalWith 3 @?= Just 1
  where
    testEvalWith n =
      lookupAtWire (OutputWire 0) $
        evalArithCircuit
          lookupAtWire
          updateAtWire
          testEqualCircuit
          (initialQapSet $ testInputMap n)

unit_splitUnsplit :: Assertion
unit_splitUnsplit =
  mapM_ (\n -> testSplitUnsplit n @?= Just n) (fmap fromIntegral [0 .. 2 ^ nbits - 1])
  where
    nbits = 16
    testSplitUnsplit n =
      lookupAtWire (OutputWire 0) $
        evalArithCircuit
          lookupAtWire
          updateAtWire
          (testSplitUnsplitCircuit nbits)
          (initialQapSet $ testInputMap n)

prop_arithCircuitValid :: ArithCircuitWithInputs Fr -> Bool
prop_arithCircuitValid (ArithCircuitWithInputs program _) =
  validArithCircuit program

prop_arithCircuitToQAP_slow :: ArithCircuitWithInputs Fr -> Property
prop_arithCircuitToQAP_slow (ArithCircuitWithInputs program inputs) =
  withMaxSuccess 10 $
    case program of
      ArithCircuit [] -> True
      ArithCircuit (_ : _) -> all testInput inputs
  where
    roots = evalFresh $ generateRoots (fromIntegral . (+ 1) <$> fresh) program
    qap = arithCircuitToQAP roots program
    testInput input =
      verifyAssignment qap $ generateAssignment program input

prop_arithCircuitToQAP_fft :: ArithCircuitWithInputs Fr -> Property
prop_arithCircuitToQAP_fft (ArithCircuitWithInputs program inputs) =
  withMaxSuccess 10 $
    case program of
      ArithCircuit [] -> True
      ArithCircuit (_ : _) -> all testInput inputs
  where
    roots = evalFresh $ generateRoots (fromIntegral . (+ 1) <$> fresh) program
    qap = createPolynomialsFFT getRootOfUnity $ arithCircuitToGenQAP roots program
    testInput input = verifyAssignment qap $ generateAssignment program input