packages feed

WringTwistree-0.1.0.0: app/Cryptanalysis.hs

{-# LANGUAGE BangPatterns #-}
module Cryptanalysis
  ( key96_0
  , key96_1
  , key96_2
  , key96_3
  , key30_0
  , key30_1
  , key30_2
  , key30_3
  , key6_0
  , key6_1
  , key6_2
  , key6_3
  , sbox30_3
  , sbox96_0
  , wring96_0
  , wring96_1
  , wring96_2
  , wring96_3
  , wring30_0
  , wring30_1
  , wring30_2
  , wring30_3
  , wring6_0
  , wring6_1
  , wring6_2
  , wring6_3
  , same30_3
  , same96_0
  , nopio
  , match
  , rot1Bit
  , bias
  , convolve
  , unbiasedConvolve
  , convolveDiff
  , big3Power
  , thueMorse
  , priminal
  , byteArray
  , b125
  , b250
  , b375
  , b500
  , b625
  , b750
  , b875
  , varConvolveDiff
  , diff1Related
  , conDiff1Related
  , diffRelated
  , conDiffRelated
  , sum1Wring
  , relatedKeyHisto
  , plaintextHisto
  , sixStatsBit
  , relatedKey
  , integralHisto
  , eightStats
  , integralCr
  , integralCrFixed
  , compressBoth
  , sixtyFourNybbleArray
  , changeEachNybble
  , compressChanges
  , collisions1
  , hashColl
  , hashCollLinear
  , cumRot
  , rotations1
  , rotations256
  , pairs
  , jiggleMatch
  ) where

import Data.Word
import Data.Bits
import Data.List (sort,sortOn,group,inits,tails,transpose)
import Math.NumberTheory.Primes
import Data.Array.Unboxed
import qualified Data.Sequence as Seq
import Data.Sequence ((><), (<|), (|>), Seq((:<|)), Seq((:|>)))
import Data.Foldable (toList,foldl')
import Control.DeepSeq
import Control.Parallel
import Control.Parallel.Strategies
import System.ProgressBar
import System.IO.Unsafe
import GHC.Conc (numCapabilities)
import qualified Data.ByteString as B
import Data.ByteString.UTF8 (fromString)
import Debug.Trace
import Cryptography.Wring
import Cryptography.Twistree
import Stats
import qualified Data.Vector.Unboxed as V

type Crypt = Wring -> V.Vector Word8 -> V.Vector Word8

key96_0 = "Водворетраванатраведрова.Нерубидрованатраведвора!"
key96_1 = "Водворетраванатраведрова.Нерубидрованатраведвора "
key96_2 = "Водворетраванатраведрова,Нерубидрованатраведвора!"
key96_3 = "Водворетраванатраведрова,Нерубидрованатраведвора "
-- Russian tongue twister
-- In the yard is grass, on the grass is wood.
-- Do not chop the wood on the grass of yard.
-- 96 bytes in UTF-8 with single bit changes.

key30_0 = "Παντοτε χαιρετε!"
key30_1 = "Πάντοτε χαιρετε!"
key30_2 = "Παντοτε χαίρετε!"
key30_3 = "Πάντοτε χαίρετε!"
-- Always rejoice! 1 Thess. 5:16.

key6_0 = "aerate"
key6_1 = "berate"
key6_2 = "cerate"
key6_3 = "derate"

sbox30_3 = sboxes (fromString key30_3)
sbox96_0 = sboxes (fromString key96_0)
same30_3 = sameBitcount sbox30_3
same96_0 = sameBitcount sbox96_0
-- These two are the longest, 17, of the sameBitcount of the S-boxes made from
-- the above keys. sameBitcount linearSbox is all 256 bytes.
-- The bitcounts of the S-box entries are:
-- same30_3: [3,4,4,2,3,4,4,4,4,4,4,3,4,4,6,4,5]
-- same96_0: [2,3,3,3,5,5,4,5,3,5,4,3,4,3,5,4,3]
-- Using 16 of the 17 bytes, drop the 5 from same30_3 (and risk the 6)
-- or drop the 2 from same96_0.

samples = 16777216
chunkSize = 1 + div samples (2 * numCapabilities)
smallChunkSize = 1 + div samples (961 * numCapabilities)

wring96_0 = keyedWring $ fromString key96_0
wring96_1 = keyedWring $ fromString key96_1
wring96_2 = keyedWring $ fromString key96_2
wring96_3 = keyedWring $ fromString key96_3
wring30_0 = keyedWring $ fromString key30_0
wring30_1 = keyedWring $ fromString key30_1
wring30_2 = keyedWring $ fromString key30_2
wring30_3 = keyedWring $ fromString key30_3
wring6_0 = keyedWring $ fromString key6_0
wring6_1 = keyedWring $ fromString key6_1
wring6_2 = keyedWring $ fromString key6_2
wring6_3 = keyedWring $ fromString key6_3

nopio :: IO () -- for calling functions which have a progress bar update
nopio = return () -- argument from GHCI

dotio :: IO ()
dotio = putStrLn "."

-- Because of rotBitcount, fixed-position bit differences are inadequate to
-- telling whether two outputs of Wring are similar. Convolve them instead.

match :: [Word8] -> [Word8] -> Int
match as bs = sum $ zipWith (\a b -> 4 - popCount (a .^. b)) as bs

rot1Bit' :: Word8 -> [Word8] -> [Word8]
rot1Bit' b [a] = [(a .>>. 1) .|. (b .<<. 7)]
rot1Bit' b (a0:a1:as) = ((a0 .>>. 1) .|. (a1 .<<. 7)) : (rot1Bit' b (a1:as))

rot1Bit :: [Word8] -> [Word8]
rot1Bit [] = []
rot1Bit (a:as) = rot1Bit' a (a:as)

-- Ranges from -1 to 1, with 0 meaning half the bits are ones.
bias :: [Word8] -> Double
bias as = 1 - ((fromIntegral $ sum $ map popCount as) / (4 * fromIntegral (length as)))

convolve :: [Word8] -> [Word8] -> [Int]
-- The lists should be equally long. Returns a list 8 times as long.
convolve as bs = take nBits $ zipWith match (repeat as) (iterate rot1Bit bs) where
  nBits = 8 * length bs

unbiasedConvolve :: [Word8] -> [Word8] -> [Double]
unbiasedConvolve as bs = map ((+(- prodBias)) . (/ halfBits) . fromIntegral)
  (convolve as bs) where
    prodBias = (bias as) * (bias bs)
    halfBits = fromIntegral (4 * (length as))

-- The numbers returned by convolveDiff appear to have mean 1 and variance
-- 0.0078125, i.e. 2/256 where 256 is the number of bits.
convolveDiff :: [Word8] -> [Word8] -> Double
convolveDiff as bs = if (scale == 0) then 1 else
  (sum $ map (^2) $ unbiasedConvolve as bs) / scale where
  scale = ((1+(bias as))*(1+(bias bs))*(1-(bias as))*(1-(bias bs)))

-- Big numbers used for generating plaintext sequences

-- Big power of 3

big3Power :: Integral a => Int -> a
big3Power n = 3 ^ (n * 53 `div` 84)

-- Thue-Morse word

log2 :: Integral a => a -> Int
log2 0 = (-1)
log2 (-1) = (-1771476585)
log2 n = 1 + (log2 (n `div` 2))

thueMorse_ :: Int -> Integer
thueMorse_ 0 = 1
thueMorse_ n = (((1 .<<. nbits) - 1 - (thueMorse_ (n-1))) .<<. nbits)
               + (thueMorse_ (n-1)) where
  nbits = (1 .<<. (n-1))

thueMorse :: Integral a => Int -> a
-- Returns a Thue-Morse word of at least n bits ending in 1.
thueMorse n = fromIntegral (thueMorse_ ((log2 n)+1))

-- Multiples of the priminal word for spreading plaintexts around the
-- space of plaintext

halfPrimes :: Int -> [Int]
halfPrimes n = takeWhile (< n) (map ((`div` 2) . (+(-3)) . unPrime) [nextPrime 3 ..])

priminal_ :: Int -> Integer
priminal_ n = sum $ map (1 .<<.) (halfPrimes n)

priminal :: Integral a => Int -> a
-- Returns a priminal word of at least n bits ending in 1.
priminal n = fromIntegral (priminal_ n)

byteArray :: (Bits a,Integral a) => Int -> a -> V.Vector Word8
byteArray len n = V.fromListN len bytes where
  bytes = map (\x -> (fromIntegral (n .>>. (8*x)) .&. 255)) [0..(len-1)]

eightByteArray :: Word64 -> V.Vector Word8
eightByteArray = byteArray 8

makeListInt :: (Bits a,Integral a) => [Word8] -> a
makeListInt [] = 0
makeListInt (n:ns) = ((makeListInt ns) .<<. 8) .|. (fromIntegral n)

makeArrayInt :: (Bits a,Integral a) => V.Vector Word8 -> a
makeArrayInt = makeListInt . V.toList

-- Random-looking bit vectors with proportions of bits set,
-- for testing convolveDiff on biased bit vectons

land :: [Word8] -> [Word8] -> [Word8]
land as bs = zipWith (.&.) as bs

lor :: [Word8] -> [Word8] -> [Word8]
lor as bs = zipWith (.|.) as bs

b125 :: Integer -> [Word8]
b125 n = land a (land b c) where
  a = V.toList $ encrypt wring6_0 $ byteArray 32 n
  b = V.toList $ encrypt wring30_0 $ byteArray 32 n
  c = V.toList $ encrypt wring96_0 $ byteArray 32 n

b250 :: Integer -> [Word8]
b250 n = land b c where
  b = V.toList $ encrypt wring30_0 $ byteArray 32 n
  c = V.toList $ encrypt wring96_0 $ byteArray 32 n

b375 :: Integer -> [Word8]
b375 n = land a (lor b c) where
  a = V.toList $ encrypt wring6_0 $ byteArray 32 n
  b = V.toList $ encrypt wring30_0 $ byteArray 32 n
  c = V.toList $ encrypt wring96_0 $ byteArray 32 n

b500 :: Integer -> [Word8]
b500 n = a where
  a = V.toList $ encrypt wring6_0 $ byteArray 32 n

b625 :: Integer -> [Word8]
b625 n = lor a (land b c) where
  a = V.toList $ encrypt wring6_0 $ byteArray 32 n
  b = V.toList $ encrypt wring30_0 $ byteArray 32 n
  c = V.toList $ encrypt wring96_0 $ byteArray 32 n

b750 :: Integer -> [Word8]
b750 n = lor b c where
  b = V.toList $ encrypt wring30_0 $ byteArray 32 n
  c = V.toList $ encrypt wring96_0 $ byteArray 32 n

b875 :: Integer -> [Word8]
b875 n = lor a (lor b c) where
  a = V.toList $ encrypt wring6_0 $ byteArray 32 n
  b = V.toList $ encrypt wring30_0 $ byteArray 32 n
  c = V.toList $ encrypt wring96_0 $ byteArray 32 n

varConvolveDiff :: (Integer -> [Word8]) -> Double
-- Assumes the mean of convolveDiff is 1
varConvolveDiff b = sum (map (\x -> (x-1)^2) sims) / 4096 where
  sims = map (\n -> convolveDiff (b (2*n)) (b (2*n+1))) [0..4095]

-- Exact bitcount:
-- b125 [21,27,48,58,61,74,82,87,91,104,112,120,142,153,156,175,180,210,213,214,251,254]
-- b250 [10,12,14,17,19,35,37,49,54,96,123,137,144,145,152,155,157,181,185,192,227,239,250]
-- b375 [15,16,63,75,79,94,99,113,141,181,186,215,249,251]
-- b500 [66,184,186,190,191,207,225,228,234,235]
-- b625 [34,45,51,53,62,77,109,164,207,249,250]
-- b750 [8,18,77,84,87,109,130,147,152,163,172,189,232,246]
-- b875 [6,13,39,45,48,52,66,71,94,95,106,108,111,122,133,161,166,173,200,203,221,228,251,254]

-- Related-key cryptanalysis
-- Take four keys which differ by one or two bytes, in pairs, and encrypt
-- the same plaintext with both and compute the difference.

-- diffRelated produces a list of integers, which are then histogrammed,
-- and the histo bars are turned into a chi-squared random variable.
-- conDiffRelated produces a list of chi-squared random variables, whose
-- mean and variance are then checked against what's expected. In both
-- cases, the chi-squared variables are scaled to have a mean of 1.

name2 :: Wring -> String -> Wring -> String
name2 w0 s w1 = (wringName w0) ++ s ++ (wringName w1)

{-# NOINLINE diff1Related #-}
diff1Related :: Wring -> Wring -> IO () -> Word64 -> Word64
diff1Related w0 w1 upd pt = seq
  (if mod pt 256 == 0 then unsafePerformIO upd else ()) $ ct0 .^. ct1 where
  ct0 = makeArrayInt $ encrypt w0 $ eightByteArray pt
  ct1 = makeArrayInt $ encrypt w1 $ eightByteArray pt

{-# NOINLINE conDiff1Related #-}
conDiff1Related :: Wring -> Wring -> IO () -> Word64 -> Double
conDiff1Related w0 w1 upd pt = seq
  (if mod pt 256 == 0 then unsafePerformIO upd else ()) $
  par ct0 $ convolveDiff ct0 ct1 where
  ct0 = V.toList $ encrypt w0 $ eightByteArray pt
  ct1 = V.toList $ encrypt w1 $ eightByteArray pt

diffRelated :: Wring -> Wring -> IO () -> [Word64]
diffRelated w0 w1 upd = traceEvent (name2 w0 "-diff-" w1) $
  map ((diff1Related w0 w1 upd) . ((priminal 64) *)) [0..]

conDiffRelated :: Wring -> Wring -> IO () -> [Double]
conDiffRelated w0 w1 upd = traceEvent (name2 w0 "-con-" w1) $
  map ((conDiff1Related w0 w1 upd) . ((priminal 64) *)) [0..]

plaintextHisto :: Histo
plaintextHisto = foldl' hCountBits (emptyHisto 64)
  (take (div samples 2) (map ((priminal 64) *) [0..]))

relatedKeyHisto :: Wring -> Wring -> IO () -> Histo
relatedKeyHisto w0 w1 upd = foldl' hCountBits (emptyHisto 64)
  (take (div samples 2) (diffRelated w0 w1 upd)
  `using` parListDeal numCapabilities rdeepseq)

relatedKeyStatBit :: Wring -> Wring -> IO () -> Double
relatedKeyStatBit w0 w1 upd = binomial (relatedKeyHisto w0 w1 upd) (div samples 2)

relatedKeyStatConv :: Wring -> Wring -> IO () -> (Double,Double)
relatedKeyStatConv w0 w1 upd = normμσ 1 (sqrt (1/32))
  (take (div samples 2) (conDiffRelated w0 w1 upd)
  `using` parListDeal numCapabilities rdeepseq)

sixStatsBit :: Wring -> Wring -> Wring -> Wring -> IO () -> [Double]
sixStatsBit w0 w1 w2 w3 upd =
  par s01 $ par s23 $ par s02 $ par s13 $ par s03 $ par s12 $
  [s01,s23,s02,s13,s03,s12] where
    s01 = relatedKeyStatBit w0 w1 upd
    s23 = relatedKeyStatBit w2 w3 upd
    s02 = relatedKeyStatBit w0 w2 upd
    s13 = relatedKeyStatBit w1 w3 upd
    s03 = relatedKeyStatBit w0 w3 upd
    s12 = relatedKeyStatBit w1 w2 upd

sixStatsConv :: Wring -> Wring -> Wring -> Wring -> IO () -> [(Double,Double)]
sixStatsConv w0 w1 w2 w3 upd =
  par (force s01) $ par (force s23) $ par (force s02) $
  par (force s13) $ par (force s03) $ par (force s12) $
  [s01,s23,s02,s13,s03,s12] where
    s01 = relatedKeyStatConv w0 w1 upd
    s23 = relatedKeyStatConv w2 w3 upd
    s02 = relatedKeyStatConv w0 w2 upd
    s13 = relatedKeyStatConv w1 w3 upd
    s03 = relatedKeyStatConv w0 w3 upd
    s12 = relatedKeyStatConv w1 w2 upd

tellStat64 :: Double -> String
-- 0.635 and 1.456 are 1% tails at 64 degrees of freedom, divided by 64.
tellStat64 stat
  | stat < 0.635 = "Too smooth. They look like a low-discrepancy sequence."
  | stat < 1.456 = "The differences look random."
  | otherwise    = "Too much variation. Check for outliers."

tellStat256 :: Double -> String
-- 0.806 and 1.217 are 1% tails at 256 degrees of freedom, divided by 256.
tellStat256 stat
  | stat < 0.806 = "Too smooth. They look like a low-discrepancy sequence."
  | stat < 1.217 = "The differences look random."
  | otherwise    = "Too much variation. Check for outliers."

tellStatμσ :: (Double,Double) -> String
tellStatμσ (mean,var)
  | mean < -0.5 = "The bit patterns are close to periodic."
  | mean > 0.5  = "The bit patterns are too similar."
  | var  > 2    = "The variance is too high."
  | var  < 0.5  = "The variance is too low."
  | otherwise   = "The differences look random."

relatedKey4Bit :: Wring -> Wring -> Wring -> Wring -> IO ()
relatedKey4Bit w0 w1 w2 w3 = do
  pb <- newProgressBar defStyle 3 (Progress 0 (div (samples*3) 256) ())
  let sixS = sixStatsBit w0 w1 w2 w3 (incProgress pb 1)
  putStrLn (show sixS)
  putStrLn ("0,1: " ++ tellStat64 (sixS !! 0))
  putStrLn ("2,3: " ++ tellStat64 (sixS !! 1))
  putStrLn ("0,2: " ++ tellStat64 (sixS !! 2))
  putStrLn ("1,3: " ++ tellStat64 (sixS !! 3))
  putStrLn ("0,3: " ++ tellStat64 (sixS !! 4))
  putStrLn ("1,2: " ++ tellStat64 (sixS !! 5))

relatedKey4Conv :: Wring -> Wring -> Wring -> Wring -> IO ()
relatedKey4Conv w0 w1 w2 w3 = do
  pb <- newProgressBar defStyle 3 (Progress 0 (div (samples*3) 256) ())
  let sixS = sixStatsConv w0 w1 w2 w3 (incProgress pb 1)
  putStrLn (show sixS)
  putStrLn ("0,1: " ++ tellStatμσ (sixS !! 0))
  putStrLn ("2,3: " ++ tellStatμσ (sixS !! 1))
  putStrLn ("0,2: " ++ tellStatμσ (sixS !! 2))
  putStrLn ("1,3: " ++ tellStatμσ (sixS !! 3))
  putStrLn ("0,3: " ++ tellStatμσ (sixS !! 4))
  putStrLn ("1,2: " ++ tellStatμσ (sixS !! 5))

relatedKey :: IO ()
relatedKey = do
  traceMarkerIO "96bit"
  putStrLn "96-byte key, 8-byte data, bitwise differences:"
  relatedKey4Bit wring96_0 wring96_1 wring96_2 wring96_3
  traceMarkerIO "96conv"
  putStrLn "96-byte key, 8-byte data, convolutional differences:"
  relatedKey4Conv wring96_0 wring96_1 wring96_2 wring96_3
  traceMarkerIO "30bit"
  putStrLn "30-byte key, 8-byte data, bitwise differences:"
  relatedKey4Bit wring30_0 wring30_1 wring30_2 wring30_3
  traceMarkerIO "30conv"
  putStrLn "30-byte key, 8-byte data, convolutional differences:"
  relatedKey4Conv wring30_0 wring30_1 wring30_2 wring30_3
  traceMarkerIO "6bit"
  putStrLn "6-byte key, 8-byte data, bitwise differences:"
  relatedKey4Bit wring6_0 wring6_1 wring6_2 wring6_3
  traceMarkerIO "6conv"
  putStrLn "6-byte key, 8-byte data, convolutional differences:"
  relatedKey4Conv wring6_0 wring6_1 wring6_2 wring6_3
  traceMarkerIO "relkey done"

-- Integral cryptanalysis
-- Take one key, and each of the eight bytes of the plaintext in parallel,
-- and xor all the ciphertexts produced by changing the byte of the plaintext
-- to all 256 possibilities.

{-# NOINLINE sum1Wring #-}
sum1Wring :: Crypt -> Wring -> IO () -> Word64 -> Int -> Word64
-- Take pt with all values in the bth byte, encrypt them all,
-- and xor the ciphertexts.
sum1Wring enc w upd pt b = seq (unsafePerformIO upd) $ foldl' xor 0 cts where
  pts = map eightByteArray $ zipWith xor (repeat pt) (map (.<<. (b .<<. 3)) [0..255])
  cts = map makeArrayInt $ map (enc w) pts

integralHisto :: Crypt -> Wring -> IO () -> Int -> Histo
integralHisto enc w upd b = foldl' hCountBits (emptyHisto 64)
  (take (div samples 256)
  (map ((\pt -> sum1Wring enc w upd pt b) . ((priminal 64) *)) [0..])
  `using` parListDeal numCapabilities rdeepseq)

integralStat :: Crypt -> Wring -> IO () -> Int -> Double
integralStat enc w upd b = binomial (integralHisto enc w upd b) (div samples 256)

eightStats :: Crypt -> Wring -> IO () -> [Double]
eightStats enc w upd =
  par s0 $ par s1 $ par s2 $ par s3 $ par s4 $ par s5 $ par s6 $ par s7 $
  [s0,s1,s2,s3,s4,s5,s6,s7] where
    s0 = integralStat enc w upd 0
    s1 = integralStat enc w upd 1
    s2 = integralStat enc w upd 2
    s3 = integralStat enc w upd 3
    s4 = integralStat enc w upd 4
    s5 = integralStat enc w upd 5
    s6 = integralStat enc w upd 6
    s7 = integralStat enc w upd 7

integral1 :: Crypt -> Wring -> IO ()
integral1 enc w = do
  pb <- newProgressBar defStyle 10 (Progress 0 (div samples 32) ())
  let eightS = eightStats enc w (incProgress pb 1)
  putStrLn (show eightS)
  putStrLn ("Byte 0: " ++ tellStat64 (eightS !! 0))
  putStrLn ("Byte 1: " ++ tellStat64 (eightS !! 1))
  putStrLn ("Byte 2: " ++ tellStat64 (eightS !! 2))
  putStrLn ("Byte 3: " ++ tellStat64 (eightS !! 3))
  putStrLn ("Byte 4: " ++ tellStat64 (eightS !! 4))
  putStrLn ("Byte 5: " ++ tellStat64 (eightS !! 5))
  putStrLn ("Byte 6: " ++ tellStat64 (eightS !! 6))
  putStrLn ("Byte 7: " ++ tellStat64 (eightS !! 7))

integralCr :: IO ()
integralCr = do
  putStrLn "96-byte key, 8-byte data:"
  integral1 encrypt wring96_0
  putStrLn "30-byte key, 8-byte data:"
  integral1 encrypt wring30_0
  putStrLn "6-byte key, 8-byte data:"
  integral1 encrypt wring6_0
  putStrLn "Linear key, 8-byte data:"
  integral1 encrypt linearWring
  putStrLn "96-byte key, byte 7:" -- This byte came out as "too much variation".
  putStrLn $ show $ integralHisto encrypt wring96_0 nopio 7
  putStrLn "Linear key, byte 3:" -- This byte came out as "low-discrepancy".
  putStrLn $ show $ integralHisto encrypt linearWring nopio 3

integralCrFixed :: IO ()
integralCrFixed = do
  putStrLn "96-byte key, 8-byte data:"
  integral1 encryptFixed wring96_0
  putStrLn "30-byte key, 8-byte data:"
  integral1 encryptFixed wring30_0
  putStrLn "6-byte key, 8-byte data:"
  integral1 encryptFixed wring6_0
  putStrLn "Linear key, 8-byte data:"
  integral1 encryptFixed linearWring

-- Attempt to find a collision of the hash compression function

compressBoth :: SBox -> V.Vector Word8 -> V.Vector Word8
-- Takes a 64-byte block, compresses it as in both the 2-tree and the 3-tree,
-- and concatenates the results.
compressBoth sbox buf = (compress sbox buf 0) <> (compress sbox buf 1)

nybbleArray :: (Bits a,Integral a) => Int -> a -> V.Vector Word8
-- Like byteArray, but the nybbles are looked up in same30_3 to get the bytes,
-- which are then assembled into a vector.
nybbleArray len n = V.fromListN len bytes where
  bytes = map (\x -> same30_3 !! (fromIntegral (n .>>. (4*x)) .&. 15)) [0..(len-1)]

sixtyFourNybbleArray :: Integer -> V.Vector Word8
sixtyFourNybbleArray = nybbleArray 64

changeOneNybble :: Integer -> Int -> [Integer]
changeOneNybble n k = map (\x -> n .^. (x .<<. (4*k))) [1..15]

changeEachNybble :: Integer -> [Integer]
-- Returns a list of 961 numbers
changeEachNybble n = foldl' (++) [n] (map (changeOneNybble n) [0..63])

compressChanges :: SBox -> Integer -> [(V.Vector Word8,V.Vector Word8)]
compressChanges sbox n = map (\x -> (x,(compressBoth sbox x))) $
  map (\x -> mix3Parts (sixtyFourNybbleArray x) 11) (changeEachNybble n)
  -- 11 is rprime for a 64-byte block

dups :: [(V.Vector Word8,V.Vector Word8)] -> [(V.Vector Word8,V.Vector Word8)]
dups [] = []
dups [x] = []
dups ((a,h0):(b,h1):xs)
  | h0==h1    = (a,b):(dups ((b,h1):xs))
  | otherwise = dups ((b,h1):xs)

{-# NOINLINE collisions1 #-}
collisions1 :: SBox -> IO () -> Integer -> [(V.Vector Word8,V.Vector Word8)]
-- Given an S-box and a 64-nybble integer, tries compressing all 961 blocks
-- made from the integer changed by 0 or 1 nybble, and returns any collisions.
collisions1 sbox upd n = seq (unsafePerformIO upd) $ dups
  $ sortOn snd $ compressChanges sbox n

collisions :: SBox -> IO () -> [(V.Vector Word8,V.Vector Word8)]
collisions sbox upd = foldl' (++) []
  (take (div samples 961)
  (map ((collisions1 sbox upd) . ((priminal 256) *)) [0..])
  `using` parListDeal numCapabilities rdeepseq)

hashColl :: IO ()
hashColl = do
  putStrLn "Hash collisions, 30-byte key:"
  pb <- newProgressBar defStyle 10 (Progress 0 (div samples 961) ())
  let colls = collisions sbox30_3 (incProgress pb 1)
  putStrLn (show colls)
  putStrLn ((show (length colls)) ++ " collisions")

hashCollLinear :: IO ()
hashCollLinear = do
  putStrLn "Hash collisions, linear S-box:"
  pb <- newProgressBar defStyle 10 (Progress 0 (div samples 961) ())
  let colls = collisions linearSbox (incProgress pb 1)
  putStrLn (show colls)
  putStrLn ((show (length colls)) ++ " collisions")

-- Clutch cryptanalysis: find out how often two messages are rotated together

clutchSamples = 1
clutchMsgLen = 10000
clutchRounds = 8
clutchParNum = 2 + numCapabilities `div` 256 -- this 256 is from rotations256

countPairs :: Ord a => [a] -> Int
-- Given a list of numbers representing the amount by which a message has been
-- rotated by some number of rounds, returns the number of pairs of
-- equal numbers.
countPairs ns = sum $ map (\n -> n*(n-1)) $ map length $ group $ sort ns

cumRot :: [Int] -> [Int]
cumRot rotations = map (`mod` (8*clutchMsgLen)) $ map sum $ tail $ inits rotations

messageArray :: Integer -> V.Vector Word8
messageArray pt = byteArray clutchMsgLen pt

rotations1 :: Wring -> Integer -> [Int]
rotations1 wring pt = cumRot $ snd $ encryptN wring clutchRounds $ messageArray pt

rotations256 :: Wring -> Integer -> Int -> [[Int]]
rotations256 wring pt n = (map (rotations1 wring) $ map (xor pt) $
  map (.<<. (8*n)) [0..255])
  `using` parListDeal numCapabilities rdeepseq

type Jiggle =
  ( Integer -- plaintext
  , Int     -- byte which is changed
  , Int     -- two byte values which give the same sum of first and second
  , Int     -- rotations, but different first and second rotations
  )

pairs1 :: [a] -> [(a,a)]
pairs1 [] = []
pairs1 (x:xs) = zip (repeat x) xs

pairs :: [a] -> [(a,a)]
pairs xs = concatMap pairs1 (tails xs)

isJiggly :: (Eq b,Eq c) => ((a,b,c),(a,b,c)) -> Bool
isJiggly ((inx0,each0,sum0),(inx1,each1,sum1)) = (sum0 == sum1) && (each0 /= each1)

jigglize :: Integer -> Int -> ((Int,b,c),(Int,b,c)) -> Jiggle
jigglize pt n ((inx0,each0,sum0),(inx1,each1,sum1)) = (pt,n,inx0,inx1)

{-# NOINLINE clutch1 #-}
clutch1 :: Fractional a => Wring -> IO () -> (Integer,Int) -> ([a],[a],[Jiggle])
clutch1 wring upd (pt,n) = (totalRotStats,togetherRotStats,jiggle) where
  rotations = seq (unsafePerformIO upd) $ rotations256 wring pt n
  rotTogether = map (tail . inits) rotations
  totalRotStats = map (/(256*255)) $ map (fromIntegral . countPairs) $ transpose rotations
  togetherRotStats = map (/(256*255)) $ map (fromIntegral . countPairs) $ transpose rotTogether
  jiggle = map (jigglize pt n) $ filter isJiggly $ pairs $
    zip3 [0..255] (map (!! 1) rotTogether) (map (!! 1) rotations)

addClutch :: Num a => ([a],[a],[b]) -> ([a],[a],[b]) -> ([a],[a],[b])
addClutch (a,b,c) (m,n,o) = (zipWith (+) a m,zipWith (+) b n,c ++ o)

divClutch :: Fractional a => ([a],[a],[b]) -> ([a],[a],[b])
divClutch (a,b,c) = (map (/(fromIntegral clutchSamples)) a,
                     map (/(fromIntegral clutchSamples)) b,
                     c)

clutchStats :: Fractional a => Wring -> ProgressBar s -> ([a],[a],[Jiggle])
clutchStats wring pb = divClutch $ foldl' addClutch (repeat 0,repeat 0,[]) $
  map (clutch1 wring (incProgress pb 1)) $
  map (\x -> ((big3Power (8*clutchMsgLen))*(fromIntegral x),
              (x*(fromIntegral (findMaxOrder (fromIntegral clutchMsgLen))) `mod` clutchMsgLen)))
  ([1..clutchSamples] `using` parListDeal clutchParNum rdeepseq)

{-# NOINLINE jiggleMatch #-}
jiggleMatch :: Wring -> IO () -> Jiggle -> (Int,Int)
jiggleMatch wring upd (pt,byte,b0,b1) = (sideways,matches) where
  (ct0,r0) = encryptN wring 2 $ messageArray (pt .^. ((fromIntegral b0) .<<. (8*byte)))
  (ct1,r1) = encryptN wring 2 $ messageArray (pt .^. ((fromIntegral b1) .<<. (8*byte)))
  sideways' = ((head r1) - (head r0)) `mod` (8*clutchMsgLen)
  sideways = if (sideways' > 4*clutchMsgLen)
                then (sideways' - 8*clutchMsgLen)
                else sideways'
  matches = seq (unsafePerformIO upd) $ match (V.toList ct0) (V.toList ct1)

clutch :: IO ()
clutch = do
  pb <- newProgressBar defStyle 10 (Progress 0 clutchSamples ())
  let (statsTotal,statsTogether,jiggles) = clutchStats wring96_0 pb
  seq (length statsTotal) nopio
  putStrLn "Same total rotation after n rounds:"
  putStrLn $ show statsTotal
  putStrLn "Rotated together for n rounds:"
  putStrLn $ show statsTogether
  putStr "Number of 2-round jiggles: "
  putStrLn $ show $ length jiggles
  pbj <- newProgressBar defStyle 10 (Progress 0 (length jiggles) ())
  let jh = jiggleHisto $ map (jiggleMatch wring96_0 (incProgress pbj 1)) jiggles
  let jStats = jiggleStats jh
  seq (sum (map (\(a,(b,c,d,e,f)) -> c) jStats)) nopio
  putStrLn $ show jStats