From 9ea718f55e8534d43a01e4b0ee2e3461b79a3883 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Ch=C3=A9ron?= Date: Sat, 4 Nov 2017 19:34:37 +0100 Subject: [PATCH] Arithmetic primitives over curve Ed25519 --- Crypto/ECC/Ed25519.hs | 232 ++++++++++++++++++++++++ cbits/ed25519/ed25519-cryptonite-exts.h | 122 +++++++++++++ cbits/ed25519/ed25519.c | 1 + cryptonite.cabal | 1 + 4 files changed, 356 insertions(+) create mode 100644 Crypto/ECC/Ed25519.hs create mode 100644 cbits/ed25519/ed25519-cryptonite-exts.h diff --git a/Crypto/ECC/Ed25519.hs b/Crypto/ECC/Ed25519.hs new file mode 100644 index 0000000..2e51537 --- /dev/null +++ b/Crypto/ECC/Ed25519.hs @@ -0,0 +1,232 @@ +-- | +-- Module : Crypto.ECC.Ed25519 +-- License : BSD-style +-- Maintainer : Olivier Chéron +-- Stability : experimental +-- Portability : unknown +-- +-- Ed25519 arithmetic primitives. +-- +{-# LANGUAGE GeneralizedNewtypeDeriving #-} +module Crypto.ECC.Ed25519 + ( Scalar + , Point + -- * Scalars + , scalarGenerate + , scalarDecodeLong + , scalarEncode + -- * Points + , pointDecode + , pointEncode + -- * Arithmetic functions + , toPoint + , pointAdd + , pointDouble + , pointMul + ) where + +import Data.Bits +import Data.Word +import Foreign.C.Types +import Foreign.Ptr +import Foreign.Storable + +import Crypto.Error +import Crypto.Internal.ByteArray (ByteArrayAccess, Bytes, + ScrubbedBytes, withByteArray) +import qualified Crypto.Internal.ByteArray as B +import Crypto.Internal.Compat +import Crypto.Internal.Imports +import Crypto.Random + + +scalarArraySize :: Int +scalarArraySize = 40 -- maximum [9 * 4 {- 32 bits -}, 5 * 8 {- 64 bits -}] + +-- | A scalar modulo order of curve Ed25519. +newtype Scalar = Scalar ScrubbedBytes + deriving (Show,NFData) + +instance Eq Scalar where + (Scalar s1) == (Scalar s2) = unsafeDoIO $ + withByteArray s1 $ \ps1 -> + withByteArray s2 $ \ps2 -> + fmap (/= 0) (ed25519_scalar_eq ps1 ps2) + {-# NOINLINE (==) #-} + +pointArraySize :: Int +pointArraySize = 160 -- maximum [4 * 10 * 4 {- 32 bits -}, 4 * 5 * 8 {- 64 bits -}] + +-- | A point on curve Ed25519. +newtype Point = Point Bytes + deriving NFData + +instance Show Point where + showsPrec d p = + let bs = pointEncode p :: Bytes + in showParen (d > 10) $ showString "Point " + . shows (B.convertToBase B.Base16 bs :: Bytes) + +instance Eq Point where + (Point p1) == (Point p2) = unsafeDoIO $ + withByteArray p1 $ \pp1 -> + withByteArray p2 $ \pp2 -> + fmap (/= 0) (ed25519_point_eq pp1 pp2) + {-# NOINLINE (==) #-} + +-- | Generate a random scalar. +scalarGenerate :: MonadRandom randomly => randomly Scalar +scalarGenerate = unwrap . scalarDecodeLong . clamp <$> generate + where + unwrap (CryptoPassed x) = x + unwrap (CryptoFailed _) = error "scalarGenerate: assumption failed" + + generate :: MonadRandom randomly => randomly ScrubbedBytes + generate = getRandomBytes 32 + + -- Uses the same bit mask than during key-generation procedure, + -- but without making divisible by 8. As a consequence of modular + -- reduction, distribution is not uniform. But the curve order is + -- very close to 2^252 so only a tiny fraction of the scalars have + -- lower probability, roughly 1/(2^126) of all possible values. + clamp :: ByteArrayAccess ba => ba -> ScrubbedBytes + clamp bs = B.copyAndFreeze bs $ \p -> do + b31 <- peekElemOff p 31 :: IO Word8 + pokeElemOff p 31 ((b31 .&. 0x7F) .|. 0x40) + +-- | Serialize a scalar to binary, i.e. a 32-byte little-endian +-- number. +-- +-- Format is binary compatible with 'Crypto.PubKey.Curve25519.SecretKey' +-- from module "Crypto.PubKey.Curve25519". +scalarEncode :: B.ByteArray bs => Scalar -> bs +scalarEncode (Scalar s) = + B.allocAndFreeze 32 $ \out -> + withByteArray s $ \ps -> ed25519_scalar_encode out ps + +-- | Deserialize a little-endian number as a scalar. Input array can +-- have any length from 0 to 64 bytes. +scalarDecodeLong :: B.ByteArrayAccess bs => bs -> CryptoFailable Scalar +scalarDecodeLong bs + | B.length bs > 64 = CryptoFailed CryptoError_EcScalarOutOfBounds + | otherwise = unsafeDoIO $ withByteArray bs initialize + where + len = fromIntegral $ B.length bs + initialize inp = do + s <- B.alloc scalarArraySize $ \ps -> + ed25519_scalar_decode_long ps inp len + return $ CryptoPassed (Scalar s) +{-# NOINLINE scalarDecodeLong #-} + +-- | Multiplies a scalar with the curve base point. +toPoint :: Scalar -> Point +toPoint (Scalar scalar) = + Point $ B.allocAndFreeze pointArraySize $ \out -> + withByteArray scalar $ \pscalar -> + ed25519_point_base_scalarmul out pscalar + +-- | Serialize a point to a 32-byte array. +-- +-- Format is binary compatible with 'Crypto.PubKey.Ed25519.PublicKey' +-- from module "Crypto.PubKey.Ed25519". +pointEncode :: B.ByteArray bs => Point -> bs +pointEncode (Point p) = + B.allocAndFreeze 32 $ \out -> + withByteArray p $ \pp -> + ed25519_point_encode out pp + +-- | Deserialize a 32-byte array as a point, ensuring the point is +-- valid on Ed25519. +-- +-- /WARNING:/ variable time +pointDecode :: B.ByteArrayAccess bs => bs -> CryptoFailable Point +pointDecode bs + | B.length bs == 32 = unsafeDoIO $ withByteArray bs initialize + | otherwise = CryptoFailed CryptoError_PointSizeInvalid + where + initialize inp = do + (res, p) <- B.allocRet pointArraySize $ \pp -> + ed25519_point_decode_vartime pp inp + if res == 0 then return $ CryptoFailed CryptoError_PointCoordinatesInvalid + else return $ CryptoPassed (Point p) +{-# NOINLINE pointDecode #-} + +-- | Add two points. +pointAdd :: Point -> Point -> Point +pointAdd (Point a) (Point b) = + Point $ B.allocAndFreeze pointArraySize $ \out -> + withByteArray a $ \pa -> + withByteArray b $ \pb -> + ed25519_point_add out pa pb + +-- | Add a point to itself. +-- +-- @ +-- pointDouble p = 'pointAdd' p p +-- @ +pointDouble :: Point -> Point +pointDouble (Point a) = + Point $ B.allocAndFreeze pointArraySize $ \out -> + withByteArray a $ \pa -> + ed25519_point_double out pa + +-- | Scalar multiplication over Ed25519. +pointMul :: Scalar -> Point -> Point +pointMul (Scalar scalar) (Point base) = + Point $ B.allocAndFreeze pointArraySize $ \out -> + withByteArray scalar $ \pscalar -> + withByteArray base $ \pbase -> + ed25519_point_scalarmul out pbase pscalar + +foreign import ccall "cryptonite_ed25519_scalar_eq" + ed25519_scalar_eq :: Ptr Scalar + -> Ptr Scalar + -> IO CInt + +foreign import ccall "cryptonite_ed25519_scalar_encode" + ed25519_scalar_encode :: Ptr Word8 + -> Ptr Scalar + -> IO () + +foreign import ccall "cryptonite_ed25519_scalar_decode_long" + ed25519_scalar_decode_long :: Ptr Scalar + -> Ptr Word8 + -> CSize + -> IO () + +foreign import ccall "cryptonite_ed25519_point_encode" + ed25519_point_encode :: Ptr Word8 + -> Ptr Point + -> IO () + +foreign import ccall "cryptonite_ed25519_point_decode_vartime" + ed25519_point_decode_vartime :: Ptr Point + -> Ptr Word8 + -> IO CInt + +foreign import ccall "cryptonite_ed25519_point_eq" + ed25519_point_eq :: Ptr Point + -> Ptr Point + -> IO CInt + +foreign import ccall "cryptonite_ed25519_point_add" + ed25519_point_add :: Ptr Point -- sum + -> Ptr Point -- a + -> Ptr Point -- b + -> IO () + +foreign import ccall "cryptonite_ed25519_point_double" + ed25519_point_double :: Ptr Point -- two_a + -> Ptr Point -- a + -> IO () + +foreign import ccall "cryptonite_ed25519_point_base_scalarmul" + ed25519_point_base_scalarmul :: Ptr Point -- scaled + -> Ptr Scalar -- scalar + -> IO () + +foreign import ccall "cryptonite_ed25519_point_scalarmul" + ed25519_point_scalarmul :: Ptr Point -- scaled + -> Ptr Point -- base + -> Ptr Scalar -- scalar + -> IO () diff --git a/cbits/ed25519/ed25519-cryptonite-exts.h b/cbits/ed25519/ed25519-cryptonite-exts.h new file mode 100644 index 0000000..5eebb17 --- /dev/null +++ b/cbits/ed25519/ed25519-cryptonite-exts.h @@ -0,0 +1,122 @@ +/* + Public domain by Olivier Chéron + + Arithmetic extensions to Ed25519-donna +*/ + + +/* + Scalar functions +*/ + +void +ED25519_FN(ed25519_scalar_encode) (unsigned char out[32], const bignum256modm in) { + contract256_modm(out, in); +} + +void +ED25519_FN(ed25519_scalar_decode_long) (bignum256modm out, const unsigned char *in, size_t len) { + expand256_modm(out, in, len); +} + +int +ED25519_FN(ed25519_scalar_eq) (const bignum256modm a, const bignum256modm b) { + bignum256modm_element_t e = 0; + + for (int i = 0; i < bignum256modm_limb_size; i++) { + e |= a[i] ^ b[i]; + } + + return (int) (1 & ((e - 1) >> bignum256modm_bits_per_limb)); +} + + +/* + Point functions +*/ + +void +ED25519_FN(ed25519_point_encode) (unsigned char r[32], const ge25519 *p) { + ge25519_pack(r, p); +} + +int +ED25519_FN(ed25519_point_decode_vartime) (ge25519 *r, const unsigned char p[32]) { + unsigned char p_neg[32]; + + // invert parity bit of X coordinate so the point is negated twice + // (once here, once in ge25519_unpack_negative_vartime) + for (int i = 0; i < 31; i++) { + p_neg[i] = p[i]; + } + p_neg[31] = p[31] ^ 0x80; + + return ge25519_unpack_negative_vartime(r, p_neg); +} + +int +ED25519_FN(ed25519_point_eq) (const ge25519 *p, const ge25519 *q) { + bignum25519 a, b; + unsigned char contract_a[32], contract_b[32]; + int eq; + + // pX * qZ = qX * pZ + curve25519_mul(a, p->x, q->z); + curve25519_contract(contract_a, a); + curve25519_mul(b, q->x, p->z); + curve25519_contract(contract_b, b); + eq = ed25519_verify(contract_a, contract_b, 32); + + // pY * qZ = qY * pZ + curve25519_mul(a, p->y, q->z); + curve25519_contract(contract_a, a); + curve25519_mul(b, q->y, p->z); + curve25519_contract(contract_b, b); + eq &= ed25519_verify(contract_a, contract_b, 32); + + return eq; +} + +void +ED25519_FN(ed25519_point_add) (ge25519 *r, const ge25519 *p, const ge25519 *q) { + ge25519_add(r, p, q); +} + +void +ED25519_FN(ed25519_point_double) (ge25519 *r, const ge25519 *p) { + ge25519_double(r, p); +} + +void +ED25519_FN(ed25519_point_base_scalarmul) (ge25519 *r, const bignum256modm s) { + ge25519_scalarmult_base_niels(r, ge25519_niels_base_multiples, s); +} + +void +ED25519_FN(ed25519_point_scalarmul) (ge25519 *r, const ge25519 *p, const bignum256modm s) { + ge25519 tmp; + uint32_t scalar_bit; + unsigned char ss[32]; + + // transform scalar as little-endian number + contract256_modm(ss, s); + + // initialize r to identity + memset(r, 0, sizeof(ge25519)); + r->y[0] = 1; + r->z[0] = 1; + + // double-add-always + for (int i = 31; i >= 0; i--) { + for (int j = 7; j >= 0; j--) { + ge25519_double(r, r); + + ge25519_add(&tmp, r, p); + scalar_bit = (ss[i] >> j) & 1; + curve25519_swap_conditional(r->x, tmp.x, scalar_bit); + curve25519_swap_conditional(r->y, tmp.y, scalar_bit); + curve25519_swap_conditional(r->z, tmp.z, scalar_bit); + curve25519_swap_conditional(r->t, tmp.t, scalar_bit); + } + } +} diff --git a/cbits/ed25519/ed25519.c b/cbits/ed25519/ed25519.c index e70ed7c..2eaab47 100644 --- a/cbits/ed25519/ed25519.c +++ b/cbits/ed25519/ed25519.c @@ -11,6 +11,7 @@ #include "ed25519.h" #include "ed25519-randombytes.h" #include "ed25519-hash.h" +#include "ed25519-cryptonite-exts.h" /* Generates a (extsk[0..31]) and aExt (extsk[32..63]) diff --git a/cryptonite.cabal b/cryptonite.cabal index 45162f1..941be68 100644 --- a/cryptonite.cabal +++ b/cryptonite.cabal @@ -121,6 +121,7 @@ Library Crypto.Data.AFIS Crypto.Data.Padding Crypto.ECC + Crypto.ECC.Ed25519 Crypto.Error Crypto.MAC.CMAC Crypto.MAC.Poly1305