pasta-curves-0.0.1.0: src/Crypto/ECC/Fields.hs
{-|
Module : Crypto.PastaCurves.Fields (internal)
Description : Supports the instantiation of parameterized prime-modulus fields.
Copyright : (c) Eric Schorn, 2022
Maintainer : eric.schorn@nccgroup.com
Stability : experimental
Portability : GHC
SPDX-License-Identifier: MIT
This internal module provides a (multi-use) field element template with an arbitrary
prime modulus along with a variety of supporting functionality such as basic arithmetic,
multiplicative inverse, square testing, square root, serialization and deserialization,
and hash2Field. The algorithms are NOT constant time; Safety and simplicity are the top
priorities.
-}
{-# LANGUAGE CPP, DataKinds, DerivingStrategies, KindSignatures, NoImplicitPrelude #-}
{-# LANGUAGE ScopedTypeVariables, Trustworthy #-}
module Fields (Field(..), Fz(..), Num(..)) where
import Prelude hiding (concat, replicate)
import Crypto.Hash (Blake2b_512 (Blake2b_512), hashWith)
import Data.Bifunctor (bimap)
import Data.Bits ((.|.), shiftL, shiftR)
import Data.ByteArray (convert, length, xor)
import Data.ByteString (concat, foldl', pack, replicate)
import Data.ByteString.UTF8 (ByteString, fromString)
import Data.Char (chr)
import Data.Tuple (swap)
import Data.Typeable (Proxy (Proxy))
import GHC.Word (Word8)
import GHC.TypeLits (KnownNat, Nat, natVal)
import System.Random (Random(randomR), RandomGen)
-- | The `Fz (z :: Nat)` field element (template) type includes a parameterized modulus
-- of @z@.
newtype Fz (z :: Nat) = Fz Integer deriving stock (Eq)
-- A CPP macro 'helper' to extract the modulus from (Fz z).
#define MOD natVal (Proxy :: Proxy z)
-- | The `Fz` type is an instance of the `Num` class.
instance KnownNat z => Num (Fz z) where
fromInteger a = Fz $ a `mod` MOD
(+) (Fz a) (Fz b) = fromInteger (a + b)
(-) (Fz a) (Fz b) = fromInteger (a - b)
(*) (Fz a) (Fz b) = fromInteger (a * b)
abs = error "abs: not implemented/needed"
signum = error "signum: not implemented/needed"
-- | The `Fz` type is an instance of the `Show` class with output in hexadecimal.
instance KnownNat z => Show (Fz z) where
show (Fz a) = "0x" ++ ["0123456789ABCDEF" !! nibble n | n <- [e, e-1..0]]
where
nibble :: Int -> Int
nibble n = fromInteger $ shiftR a (n*4) `mod` 16
e = ((3 + until ((MOD <) . (2 ^)) (+ 1) 0) `div` 4) - 1 :: Int
instance KnownNat z => Bounded (Fz z) where
minBound = 0
maxBound = fromInteger $ MOD - 1
-- | The `Field` class provides useful support functionality for field elements.
class (Num a, Eq a) => Field a where
-- | The `fromBytesF` function is the primary deserialization constructor which
-- consumes a big-endian `ByteString` sized to minimally contain the modulus
-- and returns a field element. The deserialized integer must already be properly
-- reduced to reside within [0..modulus), otherwise Nothing is returned.
fromBytesF :: ByteString -> Maybe a
-- | The `_fromBytesF` function is the secondary deserialization constructor which
-- consumes an unconstrained big-endian `ByteString` and returns a internally
-- reduced field element. This function is useful for random testing and
-- hash2Field-style functions.
_fromBytesF :: ByteString -> a
-- | The `hash2Field` function provides intermediate functionality that is suitable
-- for ultimately supporting the `Curves.hash2Curve` function. This function returns
-- a 2-tuple of field elements.
hash2Field :: ByteString -> String -> String -> (a, a)
-- | The `inv0` function returns the multiplicative inverse as calculated by Fermat's
-- Little Theorem (mapping 0 to 0).
inv0 :: a -> a
-- | The `isSqr` function indicates whether the operand has a square root.
isSqr :: a -> Bool
-- | The `rndF` function returns a random (invertible/non-zero) field element.
rndF :: (RandomGen r) => r -> (r, a)
-- | The `sgn0` function returns the least significant bit of the field element as an
-- Integer.
sgn0 :: a -> Integer
-- | The `shiftR1` function shifts the field element one bit to the right, effectively
-- dividing it by two (and discarding the remainder).
shiftR1 :: a -> a
-- | The `Fields.sqrt` function implements the variable-time Tonelli-Shanks
-- algorithm to calculate the operand's square root. The function returns `Nothing`
-- in the event of a problem (such as the operand not being a square, the modulus
-- is not prime, etc).
sqrt :: a -> Maybe a
-- | The `toBytesF` function serializes an element into a big-endian `ByteString`
-- sized to minimally contain the modulus.
toBytesF :: a -> ByteString
-- | The `toI` function returns the field element as a properly reduced Integer.
toI :: a -> Integer
-- | The `Fz z` type is an instance of the `Field` class. Several functions are largely
-- simple adapters to the more generic internal functions implemented further below.
instance KnownNat z => Field (Fz z) where
-- Validated deserialization, returns a Maybe field element. Follows section 2.3.6
-- of https://www.secg.org/sec1-v2.pdf
-- If ByteString is not the correct length or integer >= modulus, return Nothing.
-- fromBytesF :: ByteString -> Maybe a
fromBytesF bytes | Data.ByteArray.length bytes /= corLen || integer >= MOD = Nothing
| otherwise = Just $ fromInteger integer
where
corLen = (7 + until ((MOD <) . (2 ^)) (+ 1) 0) `div` 8 :: Int -- correct length
integer = foldl' (\a b -> a `shiftL` 8 .|. fromIntegral b) 0 bytes :: Integer
-- Unvalidated deserialization (no limits wrt modulus), returns reduced field element.
-- _fromBytesF :: ByteString -> a
_fromBytesF bytes = fromInteger $ foldl' (\a b -> shiftL a 8 .|. fromIntegral b) 0 bytes
-- Field-level support for the hash2Curve function, returns a pair of field elements.
-- The hash2field construction is per Zcash Pasta Curve (which is very similar but not
-- identical to the CFRG hash-to-curve specification). Fortuitously, cryptonite sets
-- the hash personalization to all zeros, see https://github.com/haskell-crypto/cryptonite/issues/333
-- Zcash/Pasta code https://github.com/zcash/pasta_curves/blob/main/src/hashtocurve.rs#L10
-- CFRG scheme (for ref) https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-14.html#name-hash_to_field-implementatio
-- Length of domain prefix and curve ID need to be less than 256 - 22
-- hash2Field :: ByteString -> String -> String -> (a, a)
hash2Field msg domPref curveId
| 22 + Prelude.length curveId + Prelude.length domPref > 255 = error "strings too long"
| otherwise = bimap _fromBytesF _fromBytesF (digest1, digest2)
where
-- Calculate reusable prefix and suffix
prefix = replicate 128 0
suffix = fromString (domPref ++ "-" ++ curveId ++ "_XMD:BLAKE2b_SSWU_RO_" ++
[chr (22 + Prelude.length curveId + Prelude.length domPref)])
-- A little helper function to hash ByteStrings
mkDigest :: ByteString -> ByteString
mkDigest x = convert $ hashWith Blake2b_512 x
-- Hash the message along with prefix, suffix, etc
digest0 = mkDigest $ concat [prefix, msg, pack [0,0x80,0], suffix]
-- Hash the hash again
digest1 = mkDigest $ concat [digest0, pack [0x01], suffix]
-- Mix the two above hashes together via bytewise XOR, then hash that too
mix = xor digest0 digest1 :: ByteString
digest2 = mkDigest $ concat [mix, pack [0x02], suffix]
-- Multiplicative inverse, with 0 mapped to 0, via Fermat's Little Theorem
-- inv0 :: a -> a
inv0 (Fz a) = Fz $ _powMod a (MOD - 2) (MOD)
-- Determines if the operand has a square root. Uses helper functions with Integers
-- isSqr :: a -> Bool
isSqr (Fz a) = _isSqr a (MOD)
-- The `rndF` function returns a random (invertible/non-zero) field element.
rndF rndGen = fromInteger <$> swap (randomR (1, MOD - 1) rndGen)
-- Returns the least significant bit of the field element as an Integer
-- sgn0 :: a -> Integer
sgn0 (Fz a) = a `mod` 2
-- Shift right by 1 (divides the element by 2, discarding the remainder)
-- shiftR1 :: a -> a
shiftR1 (Fz a) = Fz $ a `div` 2
-- Returns square root as Maybe field element. If any problems, returns Nothing.
-- sqrt :: a -> Maybe a
sqrt (Fz a) = fromInteger <$> _sqrtVt a (MOD) s p c -- Use helper function
where
-- rewrite (modulus - 1) as p * 2**s
s = until ((/= 0) . ((MOD -1) `rem`) . (2 ^)) (+ 1) 0 - 1 :: Integer
p = (MOD - 1) `div` (2 ^ s)
-- Find first non-square and use that to prepare \'fountain of fixes\'
z = head ([x | x <- [1..], not (_isSqr x (MOD))] ++ [0])
c = _powMod z p (MOD)
-- Deserialization. Follows section 2.3.7 of https://www.secg.org/sec1-v2.pdf
-- toBytesF :: a -> ByteString
toBytesF (Fz a) = pack $ reverse res
where
corLen = fromInteger $ (7 + until ((MOD <) . (2 ^)) (+ 1) 0) `div` 8 :: Int
res = [fromIntegral (shiftR a (8*b)) | b <- [0..(corLen - 1)]] :: [Word8]
-- Returns the element as an Integer
-- toI :: a -> Integer
toI (Fz a) = a
-- Complex/common support functions operating on integers rather than field elements
-- | Modular exponentiation.
-- _powMod :: operand -> exponent -> modulus
_powMod :: Integer -> Integer -> Integer -> Integer
_powMod _ e q | e < 0 || q < 2 = error "Invalid exponent/modulus"
_powMod _ 0 _ = 1
_powMod a 1 _ = a
_powMod a e q | even e = _powMod (a * a `mod` q) (shiftR e 1) q
| otherwise = a * _powMod a (e - 1) q `mod` q
-- | Is operand a square via Legendre symbol.
-- isSqr :: operand -> modulus
_isSqr :: Integer -> Integer -> Bool
_isSqr a q = (legendreSymbol == 0) || (legendreSymbol == 1)
where legendreSymbol = _powMod a ((q - 1) `div` 2) q
-- | Variable-time Tonelli-Shanks algorithm. Works with any prime modulus.
-- _sqrtVt :: operand -> modulus -> \'s\' -> \'p\' -> nonSquare
_sqrtVt :: Integer -> Integer -> Integer -> Integer -> Integer -> Maybe Integer
_sqrtVt 0 _ _ _ _ = Just 0
_sqrtVt a q _ _ _ | not (_isSqr a q) = Nothing -- Not truly necessary
_sqrtVt _ _ _ _ 0 = Nothing -- covers the bases
_sqrtVt a q s p c = Just result
where
t = _powMod a p q
r = _powMod a ((p + 1) `div` 2) q
result = loopy t r c s -- recursively iterate the function below
loopy :: Integer -> Integer -> Integer -> Integer -> Integer
loopy tt _ _ ss | tt == 0 || ss == 0 = 0
loopy 1 rr _ _ = rr
loopy tt rr cc ss = loopy t_n r_n c_n s_n -- read _n as _next
where
s_n = head ([i | i <- [1..(ss - 1)], _powMod tt (2 ^ i) q == 1] ++ [0]) -- ++0 avoids empty
ff = _powMod cc (2 ^ (ss - s_n - 1)) q
r_n = rr * ff `mod` q
t_n = (tt * _powMod ff 2 q) `mod` q
c_n = _powMod cc (2 ^ (ss - s_n)) q