content: {-# LANGUAGE RecordWildCards, NamedFieldPuns, MultiWayIf, ScopedTypeVariables #-}
  
  module Bulletproofs.InnerProductProof.Verifier (
    verifyProof,
  ) where
  
  import Protolude
  
  import qualified Data.List as L
  import qualified Data.Map as Map
  import Data.Curve.Weierstrass.SECP256K1 (PA, Fr, mul)
  
  import Bulletproofs.Utils
  
  import Bulletproofs.InnerProductProof.Internal
  
  -- | Optimized non-interactive verifier using multi-exponentiation and batch verification
  verifyProof
    :: Integer               -- ^ Range upper bound
    -> InnerProductBase PA   -- ^ Generators Gs, Hs, h
    -> PA                    -- ^ Commitment P
    -> InnerProductProof Fr PA
    -- ^ Proof that a secret committed value lies in a certain interval
    -> Bool
  verifyProof n productBase@InnerProductBase{..} commitmentLR productProof@InnerProductProof{ l, r }
    = c == cProof
    where
      (challenges, _invChallenges, c) = mkChallenges productProof commitmentLR
      otherExponents = mkOtherExponents n challenges
      cProof
        = (gsCommit `mul` l)
          <>
          (hsCommit `mul` r)
          <>
          (bH `mul` (l * r) )
  
      gsCommit = sumExps otherExponents bGs
      hsCommit = sumExps (reverse otherExponents) bHs
  
  mkChallenges
    :: InnerProductProof Fr PA
    -> PA
    -> ([Fr], [Fr], PA)
  mkChallenges InnerProductProof{ lCommits, rCommits } commitmentLR
    = foldl'
        (\(xs, xsInv, accC) (li, ri)
          -> let x = shamirX' accC li ri
                 xInv = recip x
                 c = (li `mul` (x ^ 2)) <> (ri `mul` (xInv ^ 2)) <> accC
             in (x:xs, xInv:xsInv, c)
        )
        ([], [], commitmentLR)
        (zip lCommits rCommits)
  
  mkOtherExponents :: Integer -> [Fr] -> [Fr]
  mkOtherExponents n challenges
    = Map.elems $ foldl'
        f
        (Map.fromList [(0, recip $ product challenges)])
        [0..n'-1]
    where
      n' = n `div` 2
      f acc i = foldl' (f' i) acc [0..logBase2 n-1]
  
      f' :: Integer -> Map.Map Integer Fr -> Integer -> Map.Map Integer Fr
      f' i acc' j
        = let i1 = (2^j) + i in
            if | i1 >= n -> acc'
               | Map.member i1 acc' -> acc'
               | otherwise -> Map.insert
                                i1
                                (acc' Map.! i * ((challenges L.!! fromIntegral j) ^ 2))
                                acc'
  
  
  