{-# LANGUAGE Strict #-}

-- | Cubic extension of the tower:
--
--   * Fq
--   * Fq2 := Fq[u]/u^2 + 1
--   * Fq6 := Fq2[v]/v^3 - (9 + u)
--   * Fq12 := Fq6[w]/w^2 - v
--
-- Implementation follows "Multiplication and Squaring on
-- Pairing-Friendly Fields" by Devigili, hEigeartaigh, Scott and
-- Dahab.
module Pairing.Fq6 (
  Fq6(..),
  new,
  fq6inv,
  fq6one,
  fq6zero,
  fq6sqr,
  mulXi,
  random
) where

import Protolude
import Crypto.Random (MonadRandom)

import Pairing.Fq2 (Fq2)
import qualified Pairing.Fq2 as Fq2

-- | Field extension defined as Fq2[v]/v^3 - (9 + u)
data Fq6
  = Fq6
    { fq6x :: Fq2
    , fq6y :: Fq2
    , fq6z :: Fq2
    }
  deriving (Eq, Show)

instance Num Fq6 where
  (+)         = fq6add
  (*)         = fq6mul
  negate      = fq6neg
  fromInteger = fq6int
  abs         = panic "abs not defined for fq6"
  signum      = panic "signum not defined for fq6"

instance Fractional Fq6 where
  (/) = fq6div
  fromRational (a :% b) = fq6int a / fq6int b

-- | Create a new value in @Fq6@, should be used instead of the @Fq6@
-- constructor.
new :: Fq2 -> Fq2 -> Fq2 -> Fq6
new = Fq6

-- | Additive identity
fq6zero :: Fq6
fq6zero = Fq6 0 0 0

fq6int :: Integer -> Fq6
fq6int n = Fq6 (fromInteger n) 0 0

-- | Multiplicative identity
fq6one :: Fq6
fq6one = Fq6 1 0 0

fq6add :: Fq6 -> Fq6 -> Fq6
fq6add (Fq6 x y z) (Fq6 a b c) = Fq6 (x+a) (y+b) (z+c)

fq6neg :: Fq6 -> Fq6
fq6neg (Fq6 x y z) = Fq6 (-x) (-y) (-z)

-- | Squaring operation
fq6sqr :: Fq6 -> Fq6
fq6sqr x = x^2

fq6div :: Fq6 -> Fq6 -> Fq6
fq6div a b = a * fq6inv b

fq6mul :: Fq6 -> Fq6 -> Fq6
fq6mul (Fq6 a0 a1 a2) (Fq6 b0 b1 b2) = Fq6 c0 c1 c2
  where
    t0 = a0 * b0
    t1 = a1 * b1
    t2 = a2 * b2
    c0 = Fq2.mulXi ((a1+a2) * (b1+b2) - t1 - t2) + t0
    c1 = ((a0+a1) * (b0+b1)) - t0 - t1 + Fq2.mulXi t2
    c2 = ((a0+a2) * (b0+b2)) - t0 + t1 - t2

-- | Multiply by @xi@ (cubic nonresidue in @Fq2@) and reorder
-- coefficients
{-# INLINABLE mulXi #-}
mulXi :: Fq6 -> Fq6
mulXi (Fq6 x y z) = Fq6 (z*Fq2.xi) x y

-- | Multiplicative inverse
fq6inv :: Fq6 -> Fq6
fq6inv (Fq6 a b c) = Fq6 (t*c0) (t*c1) (t*c2)
  where
    c0 = a^2 - b * c * Fq2.xi
    c1 = c^2 * Fq2.xi - a * b
    c2 = b^2 - a*c
    t  = Fq2.fq2inv ((c * c1 + b * c2) * Fq2.xi + a*c0)


random :: MonadRandom m => m Fq6
random = do
  a <- Fq2.random
  b <- Fq2.random
  c <- Fq2.random
  pure (Fq6 a b c)