diff --git a/Crypto/PubKey/Ed448.hs b/Crypto/PubKey/Ed448.hs new file mode 100644 index 0000000..0a5c069 --- /dev/null +++ b/Crypto/PubKey/Ed448.hs @@ -0,0 +1,102 @@ +-- | +-- Module : Crypto.PubKey.Ed448 +-- License : BSD-style +-- Maintainer : John Galt +-- Stability : experimental +-- Portability : unknown +-- +-- Ed448 support +-- +{-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE MagicHash #-} +module Crypto.PubKey.Ed448 + ( SecretKey + , PublicKey + , DhSecret + -- * Smart constructors + , dhSecret + , publicKey + , secretKey + -- * methods + , dh + , toPublic + ) where + +import Data.Word +import Foreign.Ptr +import GHC.Ptr + +import Crypto.Error +import Crypto.Internal.Compat +import Crypto.Internal.Imports +import Crypto.Internal.ByteArray (ByteArrayAccess, ScrubbedBytes, Bytes, withByteArray) +import qualified Crypto.Internal.ByteArray as B + +-- | A Ed448 Secret key +newtype SecretKey = SecretKey ScrubbedBytes + deriving (Show,Eq,ByteArrayAccess,NFData) + +-- | A Ed448 public key +newtype PublicKey = PublicKey Bytes + deriving (Show,Eq,ByteArrayAccess,NFData) + +-- | A Ed448 Diffie Hellman secret related to a +-- public key and a secret key. +newtype DhSecret = DhSecret ScrubbedBytes + deriving (Show,Eq,ByteArrayAccess,NFData) + +-- | Try to build a public key from a bytearray +publicKey :: ByteArrayAccess bs => bs -> CryptoFailable PublicKey +publicKey bs + | B.length bs == x448_bytes = CryptoPassed $ PublicKey $ B.copyAndFreeze bs (\_ -> return ()) + | otherwise = CryptoFailed CryptoError_PublicKeySizeInvalid + +-- | Try to build a secret key from a bytearray +secretKey :: ByteArrayAccess bs => bs -> CryptoFailable SecretKey +secretKey bs + | B.length bs == x448_bytes = unsafeDoIO $ + withByteArray bs $ \inp -> do + valid <- isValidPtr inp + if valid + then (CryptoPassed . SecretKey) <$> B.copy bs (\_ -> return ()) + else return $ CryptoFailed CryptoError_SecretKeyStructureInvalid + | otherwise = CryptoFailed CryptoError_SecretKeySizeInvalid + where + isValidPtr :: Ptr Word8 -> IO Bool + isValidPtr _ = + return True +{-# NOINLINE secretKey #-} + +-- | Create a DhSecret from a bytearray object +dhSecret :: ByteArrayAccess b => b -> CryptoFailable DhSecret +dhSecret bs + | B.length bs == x448_bytes = CryptoPassed $ DhSecret $ B.copyAndFreeze bs (\_ -> return ()) + | otherwise = CryptoFailed CryptoError_SharedSecretSizeInvalid + +-- | Compute the Diffie Hellman secret from a public key and a secret key +dh :: PublicKey -> SecretKey -> DhSecret +dh (PublicKey pub) (SecretKey sec) = DhSecret <$> + B.allocAndFreeze x448_bytes $ \result -> + withByteArray sec $ \psec -> + withByteArray pub $ \ppub -> + ccryptonite_ed448 result psec ppub +{-# NOINLINE dh #-} + +-- | Create a public key from a secret key +toPublic :: SecretKey -> PublicKey +toPublic (SecretKey sec) = PublicKey <$> + B.allocAndFreeze x448_bytes $ \result -> + withByteArray sec $ \psec -> + ccryptonite_ed448 result psec basePoint + where + basePoint = Ptr "\x05\x00\x00\x00\x00\x00\x00\x00\x00x00\x00\x00\x00\x00\x00\x00\x00x00\x00\x00\x00\x00\x00\x00\x00x00\x00\x00\x00\x00\x00\x00\x00x00\x00\x00\x00\x00\x00\x00\x00x00\x00\x00\x00\x00\x00\x00\x00x00\x00\x00\x00\x00\x00\x00"# +{-# NOINLINE toPublic #-} + +x448_bytes :: Int +x448_bytes = 448 `quot` 8 + +foreign import ccall "cryptonite_x448" + ccryptonite_ed448 :: Ptr Word8 -- ^ public + -> Ptr Word8 -- ^ secret + -> Ptr Word8 -- ^ basepoint + -> IO () diff --git a/cbits/ed448/x448.c b/cbits/ed448/x448.c new file mode 100644 index 0000000..5824fe9 --- /dev/null +++ b/cbits/ed448/x448.c @@ -0,0 +1,306 @@ +/* Copyright (c) 2015 Cryptography Research, Inc. + * Released under the MIT License. See LICENSE.txt for license information. + */ + +/** + * @file decaf.c + * @author Mike Hamburg + * @brief Decaf high-level functions. + */ + +#include +#include "x448.h" + +#define WBITS 64 /* TODO */ +#define LBITS (WBITS * 7 / 8) +#define X448_LIMBS (448/LBITS) + +#if WBITS == 64 +typedef uint64_t decaf_word_t; +typedef int64_t decaf_sword_t; +typedef __uint128_t decaf_dword_t; +typedef __int128_t decaf_sdword_t; +#elif WBITS == 32 +typedef uint32_t decaf_word_t; +typedef int32_t decaf_sword_t; +typedef uint64_t decaf_dword_t; +typedef int64_t decaf_sdword_t; +#else +#error "WBITS must be 32 or 64" +#endif + +typedef struct { decaf_word_t limb[X448_LIMBS]; } gf_s, gf[1]; + +const unsigned char X448_BASE_POINT[X448_BYTES] = {5}; + +static const gf ZERO = {{{0}}}, ONE = {{{1}}}; + +#define LMASK ((((decaf_word_t)1)<limb[i] = y->limb[i]); +} + +/** Mostly-unoptimized multiply (PERF), but at least it's unrolled. */ +static void +gf_mul (gf c, const gf a, const gf b) { + gf aa; + gf_cpy(aa,a); + + decaf_dword_t accum[X448_LIMBS] = {0}; + FOR_LIMB_U(i, { + FOR_LIMB_U(j,{ accum[(i+j)%X448_LIMBS] += (decaf_dword_t)b->limb[i] * aa->limb[j]; }); + aa->limb[(X448_LIMBS-1-i)^(X448_LIMBS/2)] += aa->limb[X448_LIMBS-1-i]; + }); + + accum[X448_LIMBS-1] += accum[X448_LIMBS-2] >> LBITS; + accum[X448_LIMBS-2] &= LMASK; + accum[X448_LIMBS/2] += accum[X448_LIMBS-1] >> LBITS; + FOR_LIMB_U(j,{ + accum[j] += accum[(j-1)%X448_LIMBS] >> LBITS; + accum[(j-1)%X448_LIMBS] &= LMASK; + }); + FOR_LIMB_U(j, c->limb[j] = accum[j] ); +} + +/** No dedicated square (PERF) */ +#define gf_sqr(c,a) gf_mul(c,a,a) + +/** Inverse square root using addition chain. */ +static void +gf_isqrt(gf y, const gf x) { + int i; +#define STEP(s,m,n) gf_mul(s,m,c); gf_cpy(c,s); for (i=0;ilimb[X448_LIMBS/2] += x->limb[X448_LIMBS-1] >> LBITS; + FOR_LIMB_U(j,{ + x->limb[j] += x->limb[(j-1)%X448_LIMBS] >> LBITS; + x->limb[(j-1)%X448_LIMBS] &= LMASK; + }); +} + +/** Add mod p. Conservatively always weak-reduce. (PERF) */ +static void +gf_add ( gf x, const gf y, const gf z ) { + FOR_LIMB_U(i, x->limb[i] = y->limb[i] + z->limb[i] ); + gf_reduce(x); +} + +/** Subtract mod p. Conservatively always weak-reduce. (PERF) */ +static void +gf_sub ( gf x, const gf y, const gf z ) { + FOR_LIMB_U(i, x->limb[i] = y->limb[i] - z->limb[i] + 2*P->limb[i] ); + gf_reduce(x); +} + +/** Constant time, if (swap) (x,y) = (y,x); */ +static void +cond_swap(gf x, gf_s *__restrict__ y, decaf_word_t swap) { + FOR_LIMB_U(i, { + decaf_word_t s = (x->limb[i] ^ y->limb[i]) & swap; + x->limb[i] ^= s; + y->limb[i] ^= s; + }); +} + +/** + * Mul by signed int. Not constant-time WRT the sign of that int. + * Just uses a full mul (PERF) + */ +static inline void +gf_mlw(gf a, const gf b, int w) { + if (w>0) { + gf ww = {{{w}}}; + gf_mul(a,b,ww); + } else { + gf ww = {{{-w}}}; + gf_mul(a,b,ww); + gf_sub(a,ZERO,a); + } +} + +/** Canonicalize */ +static void gf_canon ( gf a ) { + gf_reduce(a); + + /* subtract p with borrow */ + decaf_sdword_t carry = 0; + FOR_LIMB(i, { + carry = carry + a->limb[i] - P->limb[i]; + a->limb[i] = carry & LMASK; + carry >>= LBITS; + }); + + decaf_word_t addback = carry; + carry = 0; + + /* add it back */ + FOR_LIMB(i, { + carry = carry + a->limb[i] + (P->limb[i] & addback); + a->limb[i] = carry & LMASK; + carry >>= LBITS; + }); +} + +/* Deserialize */ +static decaf_word_t +gf_deser(gf s, const unsigned char ser[X448_BYTES]) { + unsigned int i, k=0, bits=0; + decaf_dword_t buf=0; + for (i=0; i=LBITS || i==X448_BYTES-1) && k>=LBITS) { + s->limb[k++] = buf & LMASK; + } + } + + decaf_sdword_t accum = 0; + FOR_LIMB(i, accum = (accum + s->limb[i] - P->limb[i]) >> WBITS ); + return accum; +} + +/* Serialize */ +static void +gf_ser(uint8_t ser[X448_BYTES], gf a) { + gf_canon(a); + int k=0, bits=0; + decaf_dword_t buf=0; + FOR_LIMB(i, { + buf |= (decaf_dword_t)a->limb[i]<=8 || i==X448_LIMBS-1) && k>=8) { + ser[k++]=buf; + } + }); +} + +int __attribute__((visibility("default"))) cryptonite_x448 ( + unsigned char out[X448_BYTES], + const unsigned char scalar[X448_BYTES], + const unsigned char base[X448_BYTES] +) { + gf x1, x2, z2, x3, z3, t1, t2; + gf_deser(x1,base); + gf_cpy(x2,ONE); + gf_cpy(z2,ZERO); + gf_cpy(x3,x1); + gf_cpy(z3,ONE); + + int t; + decaf_word_t swap = 0; + + for (t = 448-1; t>=0; t--) { + uint8_t sb = scalar[t/8]; + + /* Scalar conditioning */ + if (t/8==0) sb &= 0xFC; + else if (t/8 == X448_BYTES-1) sb |= 0x80; + + decaf_word_t k_t = (sb>>(t%8)) & 1; + k_t = -k_t; /* set to all 0s or all 1s */ + + swap ^= k_t; + cond_swap(x2,x3,swap); + cond_swap(z2,z3,swap); + swap = k_t; + + gf_add(t1,x2,z2); /* A = x2 + z2 */ + gf_sub(t2,x2,z2); /* B = x2 - z2 */ + gf_sub(z2,x3,z3); /* D = x3 - z3 */ + gf_mul(x2,t1,z2); /* DA */ + gf_add(z2,z3,x3); /* C = x3 + z3 */ + gf_mul(x3,t2,z2); /* CB */ + gf_sub(z3,x2,x3); /* DA-CB */ + gf_sqr(z2,z3); /* (DA-CB)^2 */ + gf_mul(z3,x1,z2); /* z3 = x1(DA-CB)^2 */ + gf_add(z2,x2,x3); /* (DA+CB) */ + gf_sqr(x3,z2); /* x3 = (DA+CB)^2 */ + + gf_sqr(z2,t1); /* AA = A^2 */ + gf_sqr(t1,t2); /* BB = B^2 */ + gf_mul(x2,z2,t1); /* x2 = AA*BB */ + gf_sub(t2,z2,t1); /* E = AA-BB */ + + gf_mlw(t1,t2,-EDWARDS_D); /* E*-d = a24*E */ + gf_add(t1,t1,z2); /* AA + a24*E */ + gf_mul(z2,t2,t1); /* z2 = E(AA+a24*E) */ + } + + /* Finish */ + cond_swap(x2,x3,swap); + cond_swap(z2,z3,swap); + gf_inv(z2,z2); + gf_mul(x1,x2,z2); + gf_ser(out,x1); + + decaf_sword_t nz = 0; + for (t=0; t>8; /* 0 = succ, -1 = fail */ + + /* return value: 0 = succ, -1 = fail */ + return nz; +} + +int __attribute__((visibility("default"))) +cryptonite_x448_base ( + unsigned char out[X448_BYTES], + const unsigned char scalar[X448_BYTES] +) { + return cryptonite_x448(out,scalar,X448_BASE_POINT); +} diff --git a/cbits/ed448/x448.h b/cbits/ed448/x448.h new file mode 100644 index 0000000..403e152 --- /dev/null +++ b/cbits/ed448/x448.h @@ -0,0 +1,25 @@ + +#define X448_BYTES (448/8) + +/* The base point (5) */ +extern const unsigned char X448_BASE_POINT[X448_BYTES]; + +/* Returns 0 on success, -1 on failure */ +int __attribute__((visibility("default"))) +cryptonite_x448 ( + unsigned char out[X448_BYTES], + const unsigned char scalar[X448_BYTES], + const unsigned char base[X448_BYTES] +); + +/* Returns 0 on success, -1 on failure + * + * Same as x448(out,scalar,X448_BASE_POINT), except that + * an implementation may optimize it. + */ +int __attribute__((visibility("default"))) +cryptonite_x448_base ( + unsigned char out[X448_BYTES], + const unsigned char scalar[X448_BYTES] +); + diff --git a/cryptonite.cabal b/cryptonite.cabal index d9ffc3e..e0328b5 100644 --- a/cryptonite.cabal +++ b/cryptonite.cabal @@ -38,6 +38,7 @@ extra-doc-files: README.md CHANGELOG.md extra-source-files: cbits/*.h cbits/aes/*.h cbits/ed25519/*.h + cbits/ed448/*.h cbits/p256/*.h cbits/blake2/ref/*.h cbits/blake2/ref/*.c @@ -121,6 +122,7 @@ Library Crypto.PubKey.ECC.P256 Crypto.PubKey.ECC.Types Crypto.PubKey.Ed25519 + Crypto.PubKey.Ed448 Crypto.PubKey.RSA Crypto.PubKey.RSA.PKCS15 Crypto.PubKey.RSA.Prim @@ -193,6 +195,7 @@ Library , cbits/cryptonite_cpu.c , cbits/curve25519/curve25519-donna.c , cbits/ed25519/ed25519.c + , cbits/ed448/x448.c , cbits/p256/p256.c , cbits/p256/p256_ec.c , cbits/cryptonite_blake2s.c diff --git a/tests/KAT_Ed448.hs b/tests/KAT_Ed448.hs new file mode 100644 index 0000000..cc1c022 --- /dev/null +++ b/tests/KAT_Ed448.hs @@ -0,0 +1,23 @@ +{-# LANGUAGE OverloadedStrings #-} +module KAT_Ed448 ( tests ) where + +import Crypto.Error +import qualified Crypto.PubKey.Ed448 as Ed448 +import Data.ByteArray as B +import Imports + +alicePrivate = throwCryptoError $ Ed448.secretKey ("\x9a\x8f\x49\x25\xd1\x51\x9f\x57\x75\xcf\x46\xb0\x4b\x58\x00\xd4\xee\x9e\xe8\xba\xe8\xbc\x55\x65\xd4\x98\xc2\x8d\xd9\xc9\xba\xf5\x74\xa9\x41\x97\x44\x89\x73\x91\x00\x63\x82\xa6\xf1\x27\xab\x1d\x9a\xc2\xd8\xc0\xa5\x98\x72\x6b" :: ByteString) +alicePublic = throwCryptoError $ Ed448.publicKey ("\x9b\x08\xf7\xcc\x31\xb7\xe3\xe6\x7d\x22\xd5\xae\xa1\x21\x07\x4a\x27\x3b\xd2\xb8\x3d\xe0\x9c\x63\xfa\xa7\x3d\x2c\x22\xc5\xd9\xbb\xc8\x36\x64\x72\x41\xd9\x53\xd4\x0c\x5b\x12\xda\x88\x12\x0d\x53\x17\x7f\x80\xe5\x32\xc4\x1f\xa0" :: ByteString) +bobPrivate = throwCryptoError $ Ed448.secretKey ("\x1c\x30\x6a\x7a\xc2\xa0\xe2\xe0\x99\x0b\x29\x44\x70\xcb\xa3\x39\xe6\x45\x37\x72\xb0\x75\x81\x1d\x8f\xad\x0d\x1d\x69\x27\xc1\x20\xbb\x5e\xe8\x97\x2b\x0d\x3e\x21\x37\x4c\x9c\x92\x1b\x09\xd1\xb0\x36\x6f\x10\xb6\x51\x73\x99\x2d" :: ByteString) +bobPublic = throwCryptoError $ Ed448.publicKey ("\x3e\xb7\xa8\x29\xb0\xcd\x20\xf5\xbc\xfc\x0b\x59\x9b\x6f\xec\xcf\x6d\xa4\x62\x71\x07\xbd\xb0\xd4\xf3\x45\xb4\x30\x27\xd8\xb9\x72\xfc\x3e\x34\xfb\x42\x32\xa1\x3c\xa7\x06\xdc\xb5\x7a\xec\x3d\xae\x07\xbd\xc1\xc6\x7b\xf3\x36\x09" :: ByteString) +aliceMultBob = "\x07\xff\xf4\x18\x1a\xc6\xcc\x95\xec\x1c\x16\xa9\x4a\x0f\x74\xd1\x2d\xa2\x32\xce\x40\xa7\x75\x52\x28\x1d\x28\x2b\xb6\x0c\x0b\x56\xfd\x24\x64\xc3\x35\x54\x39\x36\x52\x1c\x24\x40\x30\x85\xd5\x9a\x44\x9a\x50\x37\x51\x4a\x87\x9d" :: ByteString + +katTests :: [TestTree] +katTests = + [ testCase "0" (aliceMultBob @=? B.convert (Ed448.dh alicePublic bobPrivate)) + , testCase "1" (aliceMultBob @=? B.convert (Ed448.dh bobPublic alicePrivate)) + ] + +tests = testGroup "Ed448" + [ testGroup "KATs" katTests + ] diff --git a/tests/Tests.hs b/tests/Tests.hs index d16c0e3..640eeb3 100644 --- a/tests/Tests.hs +++ b/tests/Tests.hs @@ -14,6 +14,7 @@ import qualified KAT_HMAC import qualified KAT_HKDF import qualified KAT_PBKDF2 import qualified KAT_Curve25519 +import qualified KAT_Ed448 import qualified KAT_Ed25519 import qualified KAT_PubKey import qualified KAT_Scrypt @@ -37,6 +38,7 @@ tests = testGroup "cryptonite" , KAT_HMAC.tests ] , KAT_Curve25519.tests + , KAT_Ed448.tests , KAT_Ed25519.tests , KAT_PubKey.tests , testGroup "KDF"