File: Prince.hs

package info (click to toggle)
haskell-sbv 10.2-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 8,148 kB
  • sloc: haskell: 31,176; makefile: 4
file content (266 lines) | stat: -rw-r--r-- 9,381 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
-----------------------------------------------------------------------------
-- |
-- Module    : Documentation.SBV.Examples.Crypto.Prince
-- Copyright : (c) Levent Erkok
-- License   : BSD3
-- Maintainer: erkokl@gmail.com
-- Stability : experimental
--
-- Implementation of Prince encryption and decrytion, following the spec
-- <https://eprint.iacr.org/2012/529.pdf>
-----------------------------------------------------------------------------

{-# LANGUAGE DataKinds        #-}
{-# LANGUAGE ParallelListComp #-}

{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}

module Documentation.SBV.Examples.Crypto.Prince where

import Prelude hiding(round)
import Numeric

import Data.SBV
import Data.SBV.Tools.CodeGen

-- $setup
-- >>> -- For doctest purposes only:
-- >>> import Data.SBV

-- * Types
-- | Section 2: Prince is essentially a 64-bit cipher, with 128-bit key, coming in two parts.
type Block = SWord 64

-- | Plantext is simply a block.
type PT = Block

-- | Key is again a 64-bit block.
type Key = Block

-- | Cypher text is another 64-bit block.
type CT = Block

-- | A nibble is 4-bits. Ideally, we would like to represent a nibble by @SWord 4@; and indeed SBV can do that for
-- verification purposes just fine. Unfortunately, the SBV's C compiler doesn't support 4-bit bit-vectors, as
-- there's nothing meaningful in the C-land that we can map it to. Thus, we represent a nibble with 8-bits. The
-- top 4 bits will always be 0.
type Nibble = SWord 8

-- * Key expansion

-- | Expanding a key, from Section 3.4 of the spec.
expandKey :: Key -> Key
expandKey k = (k `rotateR` 1) `xor` (k `shiftR` 63)

-- | expandKey(x) = x has a unique solution. We have:
--
-- >>> prop_ExpandKey
-- Q.E.D.
prop_ExpandKey :: IO ()
prop_ExpandKey = do let lim = 10
                    ms <- extractModels <$> allSatWith z3{allSatMaxModelCount = Just lim}
                                                       (\x -> x .== expandKey x)
                    case length ms of
                      0 -> putStrLn "No solutions to equation `x == expandKey x`!"
                      1 -> putStrLn "Q.E.D."
                      n -> do let qual = if n == lim then "at least " else ""
                              putStrLn $ "Failed. There are " ++ qual ++ show n ++ " solutions to `x == expandKey x`!"
                              mapM_ (\i -> putStrLn ("    " ++ show i)) (ms :: [WordN 64])


-- | Section 2: Encryption
encrypt :: PT -> Key -> Key -> CT
encrypt pt k0 k1 = prince k0 k0' k1 pt
   where k0' = expandKey k0

-- | Decryption
decrypt :: CT -> Key -> Key -> PT
decrypt ct k0 k1 = prince k0' k0 (k1 `xor` alpha) ct
  where k0'   = expandKey k0
        alpha = 0xc0ac29b7c97c50dd

-- * Main algorithm

-- | Basic prince algorithm
prince :: Block -> Key -> Key -> Key -> Block
prince k0 k0' k1 inp = out
   where start = inp `xor` k0
         end   = princeCore k1 start
         out   = end `xor` k0'

-- | Core prince. It's essentially folding of 12 rounds stitched together:
princeCore :: Key -> Block -> Block
princeCore k1 inp = end
   where start    = inp `xor` k1 `xor` rConstants 0
         front5   = foldl (round k1) start    [1 .. 5]
         midPoint = sBoxInv . m' . sBox $ front5
         back5    = foldl (invRound k1) midPoint [6..10]
         end      = back5 `xor` rConstants 11 `xor` k1

-- | Forward round.
round :: Key -> Block -> Int -> Block
round k1 b i = k1 `xor` rConstants i `xor` m (sBox b)

-- | Backend round.
invRound :: Key -> Block -> Int -> Block
invRound k1 b i = sBoxInv (mInv (rConstants i `xor` (b `xor` k1)))

-- | M transformation.
m :: Block -> Block
m = sr . m'

-- | Inverse of M.
mInv :: Block -> Block
mInv = m' . srInv

-- | SR.
sr :: Block -> Block
sr b = fromNibbles [n0, n5, n10, n15, n4, n9, n14, n3, n8, n13, n2, n7, n12, n1, n6, n11]
  where [n0, n1, n2, n3, n4, n5, n6, n7, n8, n9, n10, n11, n12, n13, n14, n15] = toNibbles b

-- | Inverse of SR:
srInv :: Block -> Block
srInv b = fromNibbles [n0, n1, n2, n3, n4, n5, n6, n7, n8, n9, n10, n11, n12, n13, n14, n15]
  where [n0, n5, n10, n15, n4, n9, n14, n3, n8, n13, n2, n7, n12, n1, n6, n11] = toNibbles b

-- | Prove sr and srInv are inverses: We have:
--
-- >>> prove prop_sr
-- Q.E.D.
prop_sr :: Predicate
prop_sr = do b <- free "block"
             return $   b .== sr (srInv b)
                    .&& b .== srInv (sr b)

-- | M' transformation
m' :: Block -> Block
m' = mMult

-- | The matrix as described in Section 3.3
mat :: [[Int]]
mat = res
  where m0 = [[0, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]
        m1 = [[1, 0, 0, 0], [0, 0, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]
        m2 = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 0], [0, 0, 0, 1]]
        m3 = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 0]]

        rows as bs cs ds = [a ++ b ++ c ++ d | a <- as | b <- bs | c <- cs | d <- ds ]

        m0' = concat [rows m0 m1 m2 m3, rows m1 m2 m3 m0, rows m2 m3 m0 m1, rows m3 m0 m1 m2]
        m1' = concat [rows m1 m2 m3 m0, rows m2 m3 m0 m1, rows m3 m0 m1 m2, rows m0 m1 m2 m3]

        zs  = replicate 16 (replicate 16 0)
        res = concat [rows m0' zs  zs  zs, rows zs  m1' zs  zs, rows zs  zs  m1' zs, rows zs  zs  zs  m0']

-- | Multiplication.
mMult :: Block -> Block
mMult b | length mat /= 64           = error $ "mMult: Expected 64 rows, got       : " ++ show (length mat)
        | any ((/= 64) . length) mat = error $ "mMult: Expected 64 on each row, got: " ++ show [p | p@(_, l) <- zip [(1::Int)..] (map length mat), l /= 64]
        | True                       = fromBitsBE $ map mult mat
  where bits = blastBE b

        mult :: [Int] -> SBool
        mult row = foldr (.<+>) sFalse $ zipWith mul row bits

        mul :: Int -> SBool -> SBool
        mul 0 _ = sFalse
        mul 1 v = v
        mul i _ = error $ "mMult: Unexpected constant: " ++ show i

-- | Non-linear transformation of a block
nonLinear :: [Nibble] -> Nibble -> Block -> Block
nonLinear box def = fromNibbles . map s . toNibbles
  where s :: Nibble -> Nibble
        s = select box def

-- | SBox transformation.
sBox :: Block -> Block
sBox = nonLinear [0xB, 0xF, 0x3, 0x2, 0xA, 0xC, 0x9, 0x1, 0x6, 0x7, 0x8, 0x0, 0xE, 0x5, 0xD, 0x4] 0x0

-- | Inverse SBox transformation.
sBoxInv :: Block -> Block
sBoxInv = nonLinear [0xB, 0x7, 0x3, 0x2, 0xF, 0xD, 0x8, 0x9, 0xA, 0x6, 0x4, 0x0, 0x5, 0xE, 0xC, 0x1] 0x0

-- | Prove that sbox and sBoxInv are inverses: We have:
--
-- >>> prove prop_SBox
-- Q.E.D.
prop_SBox :: Predicate
prop_SBox = do b <- free "block"
               return $   b .== sBoxInv (sBox b)
                      .&& b .== sBox (sBoxInv b)

-- * Round constants

-- | Round constants
rConstants :: Int -> SWord 64
rConstants  0 = 0x0000000000000000
rConstants  1 = 0x13198a2e03707344
rConstants  2 = 0xa4093822299f31d0
rConstants  3 = 0x082efa98ec4e6c89
rConstants  4 = 0x452821e638d01377
rConstants  5 = 0xbe5466cf34e90c6c
rConstants  6 = 0x7ef84f78fd955cb1
rConstants  7 = 0x85840851f1ac43aa
rConstants  8 = 0xc882d32f25323c54
rConstants  9 = 0x64a51195e0e3610d
rConstants 10 = 0xd3b5a399ca0c2399
rConstants 11 = 0xc0ac29b7c97c50dd
rConstants n  = error $ "rConstants called with invalid round number: " ++ show n

-- | Round-constants property: rc_i `xor` rc_{11-i} is constant. We have:
--
-- >>> prop_RoundKeys
-- True
prop_RoundKeys :: SBool
prop_RoundKeys = sAnd [magic .== rConstants i `xor` rConstants (11-i) | i <- [0 .. 11]]
  where magic = rConstants 11

-- | Convert a 64 bit word to nibbles
toNibbles :: SWord 64 -> [Nibble]
toNibbles = concatMap nibbles . toBytes
  where nibbles :: SWord 8 -> [Nibble]
        nibbles b = [b `shiftR` 4, b .&. 0xF]

-- | Convert from nibbles to a 64 bit word
fromNibbles :: [Nibble] -> SWord 64
fromNibbles xs
  | length xs /= 16 = error $ "fromNibbles: Incorrect number of nibbles, expected 16, got: " ++ show (length xs)
  | True            = fromBytes $ cvt xs
  where cvt (n1 : n2 : ns) = (n1 `shiftL` 4 .|. n2) : cvt ns
        cvt _              = []

-- * Test vectors

-- | From Appendix A of the spec. We have:
--
-- >>> testVectors
-- True
testVectors :: SBool
testVectors = sAnd $  [encrypt pt k0 k1 .== ct | (pt, k0, k1, ct) <- tvs]
                   ++ [decrypt ct k0 k1 .== pt | (pt, k0, k1, ct) <- tvs]
   where tvs :: [(SWord 64, SWord 64, SWord 64, SWord 64)]
         tvs = [ (0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x818665aa0d02dfda)
               , (0xffffffffffffffff, 0x0000000000000000, 0x0000000000000000, 0x604ae6ca03c20ada)
               , (0x0000000000000000, 0xffffffffffffffff, 0x0000000000000000, 0x9fb51935fc3df524)
               , (0x0000000000000000, 0x0000000000000000, 0xffffffffffffffff, 0x78a54cbe737bb7ef)
               , (0x0123456789abcdef, 0x0000000000000000, 0xfedcba9876543210, 0xae25ad3ca8fa9ccf)
               ]

-- | Nicely show a concrete block.
showBlock :: Block -> String
showBlock b =  case unliteral b of
                 Just v  -> "0x" ++ pad (showHex v "")
                 Nothing -> error "showBlock: Symbolic input!"
  where pad s = reverse $ take 16 $ reverse s ++ repeat '0'

-- * Code generation

-- | Generating C code for the encryption block.
codeGen :: IO ()
codeGen = compileToC Nothing "enc" $ do
               input <- cgInput "inp"
               k0    <- cgInput "k0"
               k1    <- cgInput "k1"
               cgOverwriteFiles True
               cgOutput "ct"  $ encrypt input k0 k1