Arithmetic primitives over curve Ed25519

This commit is contained in:
Olivier Chéron 2017-11-04 19:34:37 +01:00
parent fcf1ff55fb
commit 9ea718f55e
4 changed files with 356 additions and 0 deletions

232
Crypto/ECC/Ed25519.hs Normal file
View File

@ -0,0 +1,232 @@
-- |
-- Module : Crypto.ECC.Ed25519
-- License : BSD-style
-- Maintainer : Olivier Chéron <olivier.cheron@gmail.com>
-- 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 ()

View File

@ -0,0 +1,122 @@
/*
Public domain by Olivier Chéron <olivier.cheron@gmail.com>
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);
}
}
}

View File

@ -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])

View File

@ -121,6 +121,7 @@ Library
Crypto.Data.AFIS
Crypto.Data.Padding
Crypto.ECC
Crypto.ECC.Ed25519
Crypto.Error
Crypto.MAC.CMAC
Crypto.MAC.Poly1305