packages feed

cardano-crypto-1.3.0: src/Crypto/Math/Edwards25519.hs

-- |
-- Module      : Crypto.Math.Edwards25519
-- Description : Edwards 25519 arithmetics
-- Maintainer  : vincent@typed.io
--
-- Simple module to play with the arithmetics of the twisted edwards curve Ed25519
-- using Extended Twisted Edwards Coordinates. Compared to the normal implementation
-- this allow to use standard DH property:
--
-- for all valid s1 and s2 scalar:
--
-- > scalarToPoint (s1 + s2) = pointAdd (scalarToPoint s1) (scalarToPoint s2)
--
-- For further useful references about Ed25519:
--
-- * RFC 8032
-- * <http://ed25519.cr.yp.to/>
-- * <http://ed25519.cr.yp.to/ed25519-20110926.pdf>
-- * <http://eprint.iacr.org/2008/522.pdf>
--
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE CPP #-}

module Crypto.Math.Edwards25519
    (
    -- * Basic types
      Scalar
    , PointCompressed
    , Signature(..)
    -- * smart constructor & destructor
    , scalar
    , scalarP
    , unScalar
    , pointCompressed
    , pointCompressedP
    , unPointCompressed
    , unPointCompressedP
    -- * Arithmetic
    , scalarFromInteger
    , scalarAdd
    , scalarToPoint
    , pointAdd
    -- * Signature & Verify
    , sign
    , verify
    ) where

import           Control.DeepSeq             (NFData)
import           Crypto.Hash
import           Crypto.Number.ModArithmetic
import           Crypto.Number.Serialize
import           Data.Bits
#if MIN_VERSION_memory(0,14,18)
import qualified Data.ByteArray              as B hiding (append, reverse)
#else
import qualified Data.ByteArray              as B hiding (append)
#endif
import           Data.ByteString             (ByteString)
import qualified Data.ByteString             as B (append, reverse)
import           Data.Hashable               (Hashable)
import           GHC.Stack

import           Crypto.Math.Bytes (Bytes)
import qualified Crypto.Math.Bytes as Bytes

-- | Represent a scalar in the base field
newtype Scalar = Scalar { unScalar :: ByteString }

-- | Represent a point on the Edwards 25519 curve
newtype PointCompressed = PointCompressed { unPointCompressed :: ByteString }
    deriving (Show, Eq, Ord, NFData, Hashable)

-- | Represent a signature
newtype Signature = Signature { unSignature :: ByteString }
    deriving (Show, Eq, Ord, NFData, Hashable)

newtype Fq = Fq { unFq :: Integer }
-- newtype Fp = Fp { unFp :: Integer }

{- for debugging
fq :: HasCallStack => Integer -> Fq
fq n
    | n >= 0 && n < q = Fq n
    | otherwise       = error "fq"
-}

fq :: Integer -> Fq
fq = Fq

-- | Create a Ed25519 scalar
--
-- Only check that the length is of expected size (32 bytes), no effort is made for the scalar
-- to be in the right base field range on purpose.
scalar :: ByteString -> Scalar
scalar bs
    | B.length bs /= 32 = error "invalid scalar"
    | otherwise         = Scalar bs

scalarP :: Bytes 32 -> Scalar
scalarP = scalar . B.pack . Bytes.unpack


-- | Check if a scalar is valid and all the bits properly set/cleared
-- scalarValid :: Scalar -> Bool
-- scalarValid _s = True -- TODO

-- | Smart constructor to create a compress point binary
--
-- Check if the length is of expected size
pointCompressed :: HasCallStack => ByteString -> PointCompressed
pointCompressed bs
    | B.length bs /= 32 = error ("invalid compressed point: expecting 32 bytes, got " ++ show (B.length bs) ++ " bytes")
    | otherwise         = PointCompressed bs

pointCompressedP :: Bytes 32 -> PointCompressed
pointCompressedP = pointCompressed . B.pack . Bytes.unpack

unPointCompressedP :: PointCompressed -> Bytes 32
unPointCompressedP (PointCompressed bs) = Bytes.pack $ B.unpack bs

-- | Create a signature using a variant of ED25519 signature
--
-- we don't hash the secret key to derive a key + prefix, but
-- instead we take an explicit salt and compute a prefix
-- using the secret key + salt.
sign :: B.ByteArrayAccess msg => Scalar -> ByteString -> msg -> Signature
sign a salt msg =
    Signature (unPointCompressed pR `B.append` toBytes s)
  where
    prefix = hash ((unScalar a) `B.append` salt) :: Digest SHA512
    pA = scalarToPoint a
    r = sha512_modq (B.convert prefix `B.append` B.convert msg)
    pR = ePointCompress $ ePointMul r pG
    h = sha512_modq (unPointCompressed pR `B.append` unPointCompressed pA `B.append` B.convert msg)
    s = (unFq r + unFq h * (fromBytes (unScalar a))) `mod` q

-- | Verify a signature
verify :: B.ByteArrayAccess msg => PointCompressed -> msg -> Signature -> Bool
verify pA msg (Signature signature) =
    pS `pointEqual` ePointAdd (ePointDecompress pR) hA
  where
    (pR, s) =
        let (sig0, sig1) = B.splitAt 32 signature
         in (PointCompressed sig0, fq $ fromBytes sig1)

    pointEqual (ExtendedPoint pX pY pZ _) (ExtendedPoint qX qY qZ _) =
        ((pX * qZ - qX * pZ) `mod` p == 0) && ((pY * qZ - qY * pZ) `mod` p == 0)

    h = sha512_modq (unPointCompressed pR `B.append` unPointCompressed pA `B.append` B.convert msg)
    pS = ePointMul s pG
    hA = ePointMul h (ePointDecompress pA)

-- | Add 2 scalar in the base field together
scalarAdd :: Scalar -> Scalar -> Scalar
scalarAdd (Scalar s1) (Scalar s2) = Scalar $ toBytes r
  where
    r = (fromBytes s1 + fromBytes s2) `mod` q

-- | Create a scalar from integer. mainly for debugging purpose.
scalarFromInteger :: Integer -> Scalar
scalarFromInteger n = Scalar $ toBytes (n `mod` q)

-- | Add 2 points together
pointAdd :: PointCompressed -> PointCompressed -> PointCompressed
pointAdd p1 p2 = ePointCompress $ ePointAdd (ePointDecompress p1) (ePointDecompress p2)

-- | Lift a scalar to the curve, and returning a compressed point
scalarToPoint :: Scalar -> PointCompressed
scalarToPoint (Scalar sec) = ePointCompress $ ePointMul (fq (fromBytes sec `mod` q)) pG

-- | Point represented by (X, Y, Z, T) in extended twisted edward coordinates.
--
--   x = X/Z
--   y = Y/Z
-- x*y = T/Z
data ExtendedPoint = ExtendedPoint !Integer !Integer !Integer !Integer
    deriving (Show,Eq)

ePointAdd :: ExtendedPoint -> ExtendedPoint -> ExtendedPoint
ePointAdd (ExtendedPoint pX pY pZ pT) (ExtendedPoint qX qY qZ qT) =
    ExtendedPoint (e*f) (g*h) (f*g) (e*h)
  where
    a = ((pY-pX) * (qY-qX)) `mod` p
    b = ((pY+pX) * (qY+qX)) `mod` p
    c = (2 * pT * qT * curveD) `mod` p
    d = (2 * pZ * qZ) `mod` p
    e = b-a
    f = d-c
    g = d+c
    h = b+a

ePointMul :: Fq -> ExtendedPoint -> ExtendedPoint
ePointMul (Fq s) = loop 255 (ExtendedPoint 0 1 1 0)
  where
    loop !i !acc !pP
        | i < 0       = pP `seq` acc
        | testBit s i = loop (i-1) (ePointAdd acc pP)  (ePointAdd pP pP)
        | otherwise   = loop (i-1) (ePointAdd acc acc) (ePointAdd acc pP)

ePointCompress :: ExtendedPoint -> PointCompressed
ePointCompress (ExtendedPoint pX pY pZ _) =
    PointCompressed $ toBytes (y .|. ((x .&. 0x1) `shiftL` 255))
  where
    zinv = modp_inv pZ
    x = (pX * zinv) `mod` p
    y = (pY * zinv) `mod` p

ePointDecompress :: PointCompressed -> ExtendedPoint
ePointDecompress (PointCompressed bs) =
    let cy    = fromBytes bs
        xSign = testBit cy 255
        y     = clearBit cy 255
        x     = recoverX y xSign
     in ExtendedPoint x y 1 ((x*y) `mod` p)

-- | Given y and the sign of x, recover x
recoverX :: Integer -> Bool -> Integer
recoverX y xSign = x''
  where
    x2 = (y*y-1) * modp_inv (curveD*y*y+1)
    x = expFast x2 ((p+3) `div` 8) p

    x'
        | (x*x - x2) `mod` p /= 0 = (x * modp_sqrt_m1) `mod` p
        | otherwise               = x

    x''
        | odd x' /= xSign = p - x'
        | otherwise       = x'

    modp_sqrt_m1 :: Integer
    !modp_sqrt_m1 = expFast 2 ((p-1) `div` 4) p

-- | Unserialize little endian
fromBytes :: ByteString -> Integer
fromBytes = os2ip . B.reverse

-- | Serialize little endian of a given size (32 bytes)
toBytes :: Integer -> ByteString
toBytes = B.reverse . i2ospOf_ 32

-- | Inverse modular p
modp_inv :: Integer -> Integer
modp_inv x = expFast x (p-2) p

-- | Base field 2^255-19 => 25519
p :: Integer
p = 2^(255 ::Int) - 19

-- | Curve constant d
curveD :: Integer
curveD = (-121665 * modp_inv 121666) `mod` p

-- | Group order
q :: Integer
q = 2^(252 ::Int) + 27742317777372353535851937790883648493

-- | Base Point in extended form
pG :: ExtendedPoint
pG = ExtendedPoint g_x g_y 1 ((g_x * g_y) `mod` p)
  where
    !g_y = (4 * modp_inv 5) `mod` p
    !g_x = recoverX g_y False

sha512_modq :: B.ByteArrayAccess ba => ba -> Fq
sha512_modq bs =
    Fq (fromBytes (B.convert (hash bs :: Digest SHA512)) `mod` q)