summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoe Crayne <joe@jerkface.net>2019-11-14 18:49:43 -0500
committerJoe Crayne <joe@jerkface.net>2019-11-14 18:49:43 -0500
commit6252bdbd0531feaa6ac9e881dffe5c92b8b40197 (patch)
treea36167c0739ee8c3bd4f36658ae1b3c8e0e6c333
parentb42c0d847a785487f3222b0d5360746d25d3209c (diff)
XEd25519 signature algorithm. (Signatures using montgomery cv25519 keys).
-rw-r--r--Crypto/XEd25519.hs193
-rw-r--r--Crypto/XEd25519/FieldElement.hs49
-rw-r--r--Data/OpenPGP/Util/Cv25519.hs2
-rw-r--r--Data/OpenPGP/Util/Verify.hs14
-rw-r--r--openpgp-util.cabal5
5 files changed, 260 insertions, 3 deletions
diff --git a/Crypto/XEd25519.hs b/Crypto/XEd25519.hs
new file mode 100644
index 0000000..1088347
--- /dev/null
+++ b/Crypto/XEd25519.hs
@@ -0,0 +1,193 @@
1module Crypto.XEd25519 where
2
3import Control.Arrow
4import Data.Bits
5import Data.ByteArray as BA
6import Data.Memory.PtrMethods (memCopy)
7import Crypto.Hash
8import Crypto.ECC.Edwards25519
9import Crypto.Error
10import qualified Crypto.PubKey.Ed25519 as Ed25519
11import Foreign.Marshal
12import Foreign.Ptr
13import Foreign.Storable
14import qualified Crypto.PubKey.Curve25519 as X25519
15
16import Crypto.XEd25519.FieldElement
17-- import Crypto.Nonce
18
19
20data SecretKey = SecretKey { secretScalar :: Scalar }
21
22data PublicKey = PublicKey Ed25519.PublicKey
23 deriving Eq
24
25-- type Nonce = Nonce32
26type Nonce = Bytes -- 32 bytes
27
28newtype EncodedPoint = EncodedPoint Point
29
30instance ByteArrayAccess SecretKey where
31 length _ = 32
32 withByteArray (SecretKey scalar) = withByteArray (scalarEncode scalar :: Bytes)
33
34instance ByteArrayAccess PublicKey where
35 length _ = 32
36 withByteArray (PublicKey edpub) = withByteArray edpub
37
38instance ByteArrayAccess EncodedPoint where
39 length _ = 32
40 withByteArray (EncodedPoint pt) f =
41 withByteArray (pointEncode pt :: Bytes) f
42
43
44data Signature = Signature EncodedPoint Scalar
45
46instance ByteArrayAccess Signature where
47 length _ = 64
48 withByteArray (Signature pt scalar) f =
49 withByteArray pt $ \ptptr -> do
50 withByteArray (SecretKey scalar) $ \scalarptr -> do
51 allocaBytes 64 $ \ptr -> do
52 memCopy ptr ptptr 32
53 memCopy (ptr `plusPtr` 32) scalarptr 32
54 f (castPtr ptr)
55
56signatureDecode :: ByteArrayAccess ba => ba -> Maybe Signature
57signatureDecode bs = do
58 let (ptbs,scbs) = BA.splitAt 32 ( BA.convert bs :: Bytes)
59 pt <- maybeCryptoError $ pointDecode ptbs
60 sc <- maybeCryptoError $ scalarDecodeLong scbs
61 return $ Signature (ge_p3_tobytes pt) sc
62
63
64padding :: Bytes
65padding = 0xFE `BA.cons` BA.replicate 31 0xFF
66
67sign :: ByteArrayAccess dta => dta -> Nonce -> SecretKey -> PublicKey -> Signature
68sign dta nonce sec pub = Signature rB s
69 where
70 rB = ge_p3_tobytes $ ge_scalarmult_base r
71
72 r = sc_reduce $ hashFinalize $ (`hashUpdate` padding)
73 >>> (`hashUpdate` sec)
74 >>> (`hashUpdate` dta)
75 >>> (`hashUpdate` nonce) $ hashInit
76
77 h = sc_reduce $ hashFinalize $ (`hashUpdate` rB)
78 >>> (`hashUpdate` pub)
79 >>> (`hashUpdate` dta) $ hashInit
80
81 -- s = r + ha (mod q)
82 s = sc_muladd h (secretScalar sec) r
83
84
85
86ge_p3_tobytes :: Point -> EncodedPoint
87ge_p3_tobytes = EncodedPoint
88
89ge_scalarmult_base :: Scalar -> Point
90ge_scalarmult_base = toPoint
91
92sc_muladd :: Scalar -> Scalar -> Scalar -> Scalar
93sc_muladd a b c = scalarAdd (scalarMul a b) c
94
95sc_reduce :: Digest SHA512 -> Scalar
96sc_reduce digest = x where CryptoPassed x = scalarDecodeLong digest -- ???
97
98-- Scalar is internally, at least on 64bit machines, represented as 5
99-- 56-bit words in little-endian order, each encoded as a Word64.
100sc_neg :: Scalar -> Scalar
101sc_neg = scalarMul sc_neg1
102
103verify :: ByteArrayAccess dta => PublicKey -> dta -> Signature -> Bool
104verify pub dta signature = Ed25519.verify ed_pub dta ed_sig
105 where
106 CryptoPassed ed_pub = Ed25519.publicKey pub'
107 CryptoPassed ed_sig = Ed25519.signature signature'
108
109 -- Get the sign bit from the s part of the signature.
110 sign_bit = BA.index signature 63 .&. 0x80
111
112 -- Set the sign bit to zero in the s part of the signature.
113 signature' :: Bytes
114 signature' = BA.copyAndFreeze signature $ \ptr -> do
115 let at63 = plusPtr ptr 63
116 byte63 <- peek at63
117 poke at63 $ byte63 .&. (0x7F `asTypeOf` sign_bit)
118
119 -- Restore the sign bit on the verification key, which should have 0 as its
120 -- current sign bit.
121 pub' :: Bytes
122 pub' = BA.copyAndFreeze pub $ \ptr -> do
123 let at31 = plusPtr ptr 31
124 byte31 <- peek at31
125 poke at31 $ (byte31 .&. 0x7F) .|. sign_bit
126
127
128-- typedef crypto_int32 fe[10];
129--
130-- fe means field element. Here the field is \Z/(2^255-19).
131-- An element t, entries t[0]...t[9], represents the integer
132-- t[0]+2^26 t[1]+2^51 t[2]+2^77 t[3]+2^102 t[4]+...+2^230 t[9].
133-- Bounds on each t[i] vary depending on context.
134
135-- mont_pub_to_ed_pub
136toSigningKey :: X25519.PublicKey -> PublicKey
137toSigningKey mont_pub0 = PublicKey ed_pub
138 where
139 -- Read the public key as a field element
140 mont_pub = fe_frombytes mont_pub0
141
142 -- Convert the Montgomery public key to a twisted Edwards public key
143 fe_ONE = fe_1
144
145 -- Calculate the parameters (u - 1) and (u + 1)
146 mont_pub_minus_one = fe_sub mont_pub fe_ONE
147 mont_pub_plus_one0 = fe_add mont_pub fe_ONE
148
149 -- Prepare inv(u + 1)
150 mont_pub_plus_one = fe_invert mont_pub_plus_one0
151
152 -- Calculate y = (u - 1) * inv(u + 1) (mod p)
153 ed_pub0 = fe_mul mont_pub_minus_one mont_pub_plus_one
154 ed_pub = fe_tobytes ed_pub0
155
156-- mont_priv_to_ed_pair
157toSigningKeyPair :: X25519.SecretKey -> (SecretKey,PublicKey)
158toSigningKeyPair mont_priv0 = (SecretKey ed_priv, PublicKey ed_pub)
159 where
160 -- Prepare a buffer for the twisted Edwards private key
161 ed_priv1 = (throwCryptoError . scalarDecodeLong :: X25519.SecretKey -> Scalar) mont_priv0
162
163 -- Get the twisted edwards public key, including the sign bit
164 ed_pub0 = ge_p3_tobytes $ ge_scalarmult_base ed_priv1
165
166 -- Save the sign bit for later
167 sign_bit = (BA.index ed_pub0 31 `shiftR` 7) .&. 1
168
169 -- Force the sign bit to zero
170 pub' :: Bytes
171 pub' = BA.copyAndFreeze ed_pub0 $ \ptr -> do
172 let at31 = plusPtr ptr 31
173 byte31 <- peek at31
174 poke at31 $ (byte31 .&. 0x7F) `asTypeOf` sign_bit
175
176 CryptoPassed ed_pub = Ed25519.publicKey pub'
177
178
179 -- Prepare the negated private key
180 ed_priv_neg = sc_neg ed_priv1
181
182 -- Get the correct private key based on the sign stored above
183 ed_priv = if sign_bit/=0 then ed_priv_neg
184 else ed_priv1
185
186-- sc_zero = throwCryptoError $ scalarDecodeLong (b::Bytes)
187-- where
188-- b = BA.pack $ encodeLittleEndian $ 2^252 + 27742317777372353535851937790883648493
189
190sc_neg1 :: Scalar
191sc_neg1 = throwCryptoError $ scalarDecodeLong (b::Bytes)
192 where
193 b = BA.pack $ encodeLittleEndian $ 2^252 + 27742317777372353535851937790883648492
diff --git a/Crypto/XEd25519/FieldElement.hs b/Crypto/XEd25519/FieldElement.hs
new file mode 100644
index 0000000..7a91610
--- /dev/null
+++ b/Crypto/XEd25519/FieldElement.hs
@@ -0,0 +1,49 @@
1{-# LANGUAGE DataKinds #-}
2{-# LANGUAGE TypeOperators #-}
3module Crypto.XEd25519.FieldElement where
4
5import Crypto.Error
6import qualified Crypto.PubKey.Curve25519 as X25519
7import qualified Crypto.PubKey.Ed25519 as Ed25519
8import Data.ByteArray as BA (pack,unpack,Bytes)
9import Data.Modular
10import Data.Word
11
12-- 2^255 - 19
13type P25519 = 57896044618658097711785492504343953926634992332820282019728792003956564819949
14
15newtype FieldElement = FE (ℤ / P25519)
16
17
18fe_frombytes :: X25519.PublicKey -> FieldElement
19fe_frombytes pub = FE $ toMod $ decodeLittleEndian $ BA.unpack pub
20
21fe_tobytes :: FieldElement -> Ed25519.PublicKey
22fe_tobytes (FE x) = throwCryptoError $ Ed25519.publicKey (b :: Bytes)
23 where
24 b = BA.pack $ take 32 $ (encodeLittleEndian $ unMod x) ++ repeat 0
25
26fe_1 :: FieldElement
27fe_1 = FE $ toMod 1
28
29fe_sub :: FieldElement -> FieldElement -> FieldElement
30fe_sub (FE x) (FE y) = FE $ x - y
31
32fe_add :: FieldElement -> FieldElement -> FieldElement
33fe_add (FE x) (FE y) = FE $ x + y
34
35fe_invert :: FieldElement -> FieldElement
36fe_invert (FE x) = FE $ inv x
37
38fe_mul :: FieldElement -> FieldElement -> FieldElement
39fe_mul (FE x) (FE y) = FE (x * y)
40
41decodeLittleEndian :: [Word8] -> Integer
42decodeLittleEndian [] = 0
43decodeLittleEndian (x:xs) = fromIntegral x + 256 * decodeLittleEndian xs
44
45encodeLittleEndian :: Integer -> [Word8]
46encodeLittleEndian 0 = []
47encodeLittleEndian x = let (bs,b) = divMod x 256
48 in fromIntegral b : encodeLittleEndian bs
49
diff --git a/Data/OpenPGP/Util/Cv25519.hs b/Data/OpenPGP/Util/Cv25519.hs
index aef3521..4900b2f 100644
--- a/Data/OpenPGP/Util/Cv25519.hs
+++ b/Data/OpenPGP/Util/Cv25519.hs
@@ -17,7 +17,7 @@ import Numeric
17import Data.Int 17import Data.Int
18 18
19import Data.OpenPGP.Internal 19import Data.OpenPGP.Internal
20import Data.OpenPGP.Util 20import Data.OpenPGP.Util.Fingerprint
21import Data.OpenPGP.Util.Base 21import Data.OpenPGP.Util.Base
22import Data.OpenPGP as OpenPGP 22import Data.OpenPGP as OpenPGP
23import Crypto.Cipher.SBox 23import Crypto.Cipher.SBox
diff --git a/Data/OpenPGP/Util/Verify.hs b/Data/OpenPGP/Util/Verify.hs
index 5eea260..66db2ab 100644
--- a/Data/OpenPGP/Util/Verify.hs
+++ b/Data/OpenPGP/Util/Verify.hs
@@ -20,7 +20,8 @@ import Crypto.PubKey.HashDescr
20 20
21import Data.OpenPGP.Util.Base 21import Data.OpenPGP.Util.Base
22import Data.OpenPGP.Util.Ed25519 22import Data.OpenPGP.Util.Ed25519
23 23import Data.OpenPGP.Util.Cv25519 as Cv25519
24import Crypto.XEd25519 as Xed25519
24 25
25dsaKey :: OpenPGP.Packet -> Vincent.DSA.PublicKey 26dsaKey :: OpenPGP.Packet -> Vincent.DSA.PublicKey
26dsaKey k = Vincent.DSA.PublicKey 27dsaKey k = Vincent.DSA.PublicKey
@@ -55,9 +56,20 @@ verifyOne keys sig over = fmap (const sig) $ maybeKey >>= verification >>= guard
55 OpenPGP.DSA -> dsaVerify 56 OpenPGP.DSA -> dsaVerify
56 OpenPGP.ECDSA -> ecdsaVerify 57 OpenPGP.ECDSA -> ecdsaVerify
57 OpenPGP.Ed25519 -> ed25519Verify sig over 58 OpenPGP.Ed25519 -> ed25519Verify sig over
59 OpenPGP.ECC -> xed25519Verify
58 alg | alg `elem` [OpenPGP.RSA,OpenPGP.RSA_S] -> rsaVerify 60 alg | alg `elem` [OpenPGP.RSA,OpenPGP.RSA_S] -> rsaVerify
59 | otherwise -> const Nothing 61 | otherwise -> const Nothing
60 62
63 xed25519Verify k = do
64 guard $ oid_cv25519 == keyParam 'c' k
65 cvk <- cv25519Key $ OpenPGP.key k
66 let xed = Xed25519.toSigningKey cvk
67 -- verify :: ByteArrayAccess dta => PublicKey -> dta -> Signature -> Bool
68 let hashbs = hashBySymbol (OpenPGP.hash_algorithm sig) $ LZ.fromChunks [over]
69 edsig <- ed25519sig sig
70 xedsig <- Xed25519.signatureDecode edsig
71 Just $ Xed25519.verify xed hashbs xedsig
72
61#if defined(VERSION_cryptonite) 73#if defined(VERSION_cryptonite)
62 dsaVerify k = let k' = dsaKey k in 74 dsaVerify k = let k' = dsaKey k in
63 -- XXX: What happened to dsaTruncate? 75 -- XXX: What happened to dsaTruncate?
diff --git a/openpgp-util.cabal b/openpgp-util.cabal
index ae8d373..132cb05 100644
--- a/openpgp-util.cabal
+++ b/openpgp-util.cabal
@@ -130,6 +130,8 @@ library
130 Crypto.Cipher.SBox 130 Crypto.Cipher.SBox
131 Crypto.Cipher.ThomasToVincent 131 Crypto.Cipher.ThomasToVincent
132 Crypto.JOSE.AESKW 132 Crypto.JOSE.AESKW
133 Crypto.XEd25519
134 Crypto.XEd25519.FieldElement
133 other-modules: 135 other-modules:
134 Data.OpenPGP.Internal 136 Data.OpenPGP.Internal
135 Data.OpenPGP.Util.Fingerprint 137 Data.OpenPGP.Util.Fingerprint
@@ -153,7 +155,8 @@ library
153 vector >=0.9, 155 vector >=0.9,
154 tagged >=0.4.2.1, 156 tagged >=0.4.2.1,
155 cereal >=0.3.0, 157 cereal >=0.3.0,
156 integer-gmp 158 integer-gmp,
159 modular-arithmetic
157 160
158 if flag(cryptonite) 161 if flag(cryptonite)
159 build-depends: 162 build-depends: