packages feed

crypto-multihash 0.4.1.0 → 0.4.2.0

raw patch · 7 files changed

+202/−56 lines, 7 filesdep +QuickCheck

Dependencies added: QuickCheck

Files

README.md view
@@ -13,9 +13,7 @@ This library is still experimental and the api is not guaranteed stable.  I will increment the version number appropriately in case of breaking changes. -For the moment the library implements all the expected hashing algorithms with the exception of shake-128 and shake-256. A Multihash can be encoded in hex (`Base16`), bitcoin base58 (`Base58`) and base64 (`Base64`). --The `Base32` encoding is not yet supported due to discrepancy between the encoding from `Data.ByteArray.Encoding` and the one appearing in the official multihash page.+For the moment the library implements all the expected hashing algorithms with the exception of shake-128 and shake-256. A Multihash can be encoded in hex (`Base16`), base 32 (`Base32`), bitcoin base58 (`Base58`) and base64 (`Base64`).   # Usage @@ -81,7 +79,7 @@ or read data from the standard input   ```{.bash}-echo -n test | stack exec mh -- -`+echo -n test | stack exec mh -- - ```  # Contribution@@ -97,8 +95,10 @@  # TODO -- Use the hash length in `checkPayload` to treat correctly truncated hashes (see https://github.com/jbenet/multihash/issues/1#issuecomment-91783612)+- ~~Test the new `getBase` implementation using quickcheck~~+- Accurately test the correct support of truncated multihashes, including the truncation length that triggers easy failures in `getBase`+- Implement benchmarks, then start optimising the code where possible+- ~~Use the hash length in `checkPayload` to treat correctly truncated hashes (see https://github.com/jbenet/multihash/issues/1#issuecomment-91783612)~~ - Improve documentation-- Improve testing for for raised exceptions - Implement `shake-128` and `shake-256` multihashes-- Implement `Base32` encoding waiting for https://github.com/jbenet/multihash/issues/31 to be resolved)+- ~~Implement `Base32` encoding~~ waiting for https://github.com/jbenet/multihash/issues/31 to be resolved)
app/Main.hs view
@@ -16,14 +16,14 @@ main = do   (_args, files) <- getArgs >>= parse   mapM_ printers files-  putStrLn "Done! Note: shake-128/256 and Base32 are not yet part of the library"+  putStrLn "Done! Note: shake-128/256 are not yet part of the library"   printer :: (HashAlgorithm a, Codable a, Show a) => a -> ByteString -> IO () printer alg bs = do   let m = multihash alg bs   putStrLn $ printf "Hash algorithm: %s" (show alg)   putStrLn $ printf "Base16: %s" (encode' Base16 m :: String)-  -- Base32 missing+  putStrLn $ printf "Base32: %s" (encode' Base32 m :: String)   putStrLn $ printf "Base58: %s" (encode' Base58 m :: String)   putStrLn $ printf "Base64: %s" (encode' Base64 m :: String)   putStrLn ""
crypto-multihash.cabal view
@@ -1,5 +1,5 @@ name:                crypto-multihash-version:             0.4.1.0+version:             0.4.2.0 synopsis:            Multihash library on top of cryptonite crypto library description:         Multihash is a protocol for encoding the hash algorithm                      and digest length at the start of the digest, see the official@@ -50,6 +50,7 @@                      , bytestring                      , crypto-multihash                      , hspec+                     , QuickCheck   ghc-options:         -threaded -rtsopts -with-rtsopts=-N   default-language:    Haskell2010 
src/Crypto/Multihash.hs view
@@ -19,7 +19,7 @@ -- {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleContexts  #-}  -- TODO: use length in check* to treat correctly truncated hashes -- see https://github.com/jbenet/multihash/issues/1#issuecomment-91783612@@ -35,8 +35,11 @@   -- * Multihash helpers   , multihash   , multihashlazy+  , truncatedMultihash+  , truncatedMultihash'   , checkMultihash   , checkMultihash'+  , getBase   -- * Re-exported types   , HashAlgorithm   , SHA1(..)@@ -78,12 +81,14 @@  instance (HashAlgorithm a, Codable a) => Encodable (MultihashDigest a) where   encode base (MultihashDigest alg len md) = -    if len == BA.length md-      then do+    -- lenght can be shorter to allow truncated multihash+    if len <=0 || len > BA.length md+      then+        Left "Corrupted MultihashDigest: invalid length"+      else do         d <- fullDigestUnpacked         return $ fromString $ map (toEnum . fromIntegral) d-      else -        Left "Corrupted MultihashDigest: invalid length"+              where       fullDigestUnpacked :: Either String [Word8]@@ -99,7 +104,7 @@           dSize :: Word8           dSize = fromIntegral len           dTail :: Bytes-          dTail = BA.convert md+          dTail = BA.take len (BA.convert md)    check hash_ multihash_ = let hash_' = convertString hash_ in do     base <- getBase hash_'@@ -137,14 +142,35 @@ multihash alg bs = let digest = hash bs                    in MultihashDigest alg (BA.length digest) digest ++-- | Helper to multihash a 'ByteArrayAccess' using a supported hash algorithm. +--   Uses 'Crypto.Hash.hash' for hashing and truncates the hash to the lenght +--   specified (must be positive and not longer than the digest length).+truncatedMultihash :: (HashAlgorithm a, Codable a, ByteArrayAccess bs) +                      => Int -> a -> bs -> Either String (MultihashDigest a)+truncatedMultihash len alg bs = let digest = hash bs in +                  if len <= 0 || len > BA.length digest+                    then Left "invalid truncated multihash lenght"+                    else Right $ MultihashDigest alg len digest++-- | Unsafe helper to multihash a 'ByteArrayAccess' using a supported hash algorithm. +--   Uses 'Crypto.Hash.hash' for hashing and truncates the hash to the lenght +--   specified (must be positive and not longer than the digest length, otherwise+--   the function will throw an error).+truncatedMultihash' :: (HashAlgorithm a, Codable a, ByteArrayAccess bs) +                      => Int -> a -> bs -> MultihashDigest a+truncatedMultihash' len alg bs = eitherToErr $ truncatedMultihash len alg bs+ ------------------------------------------------------------------------------- --- | 'checkPayload' wrapper for API retro-compatibility+-- | Safely check the correctness of an encoded 'Encodable' against the +--   corresponding data. checkMultihash :: (IsString s, ConvertibleStrings s BS.ByteString, ByteArrayAccess bs)                   => s -> bs -> Either String Bool checkMultihash h p = checkPayload h (Payload p) --- | Unsafe 'checkPayload' wrapper for API retro-compatibility+-- | Unsafe version of 'checkMultihash'. +--   Throws on encoding/decoding errors instead of returning an 'Either' type. checkMultihash' :: (IsString s, ConvertibleStrings s BS.ByteString, ByteArrayAccess bs)                    => s -> bs -> Bool checkMultihash' h p = checkPayload' h (Payload p)@@ -155,7 +181,7 @@ --   a 'MultihashDigest' and uses it to binary encode the data in a 'MultihashDigest'. getBinaryEncodedMultihash :: (ByteArrayAccess bs, IsString s)                               => BS.ByteString -> bs -> Either String s-getBinaryEncodedMultihash mhd uh = let bitOne = head $ BA.unpack mhd in+getBinaryEncodedMultihash mhd uh =    case elemIndex bitOne hashCodes of     Just 0 -> rs SHA1 uh     Just 1 -> rs SHA256 uh@@ -169,7 +195,9 @@     Just _ -> Left "This should be impossible"     Nothing -> Left "Impossible to infer the appropriate hash from the header"   where -    rs alg = encode Base2 . multihash alg+    [bitOne, bitTwo] = take 2 $ BA.unpack mhd+    rs alg s = truncatedMultihash (fromIntegral bitTwo) alg s >>= encode Base2+         hashCodes :: [Word8]     hashCodes = map fromIntegral                     ([0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x40, 0x41]::[Int])
src/Crypto/Multihash/Internal.hs view
@@ -13,6 +13,7 @@ import qualified Data.ByteArray.Encoding as BE import qualified Data.ByteString as BS import qualified Data.ByteString.Base58 as B58+import Data.List (elemIndices) import Data.Word (Word8)  -------------------------------------------------------------------------------@@ -34,28 +35,59 @@ convertFromBase b bs = case b of   Base2  -> Left "This is not supposed to happen"   Base16 -> BE.convertFromBase BE.Base16 bs-  Base32 -> Left "Base32 decoder not implemented"+  Base32 -> BE.convertFromBase BE.Base32 bs   Base58 -> do     dec <- maybeToEither "Base58 decoding error" (B58.decodeBase58 B58.bitcoinAlphabet bs)     return (BA.convert dec)   Base64 -> BE.convertFromBase BE.Base64 bs --- | Infer the 'Base' encoding function from an encoded 'BS.BinaryString' representing --- a 'MultihashDigest'.+-- | Infer the 'Base' encoding function from an encoded 'BS.BinaryString'.+--   Supports only 'Base16', bitcoin 'Base58' and 'Base64' for the moment.+--   NOTE: it can fail or _infer the wrong encoding_ if the string is too short+--   or if it is encoded using a different standard. getBase :: BS.ByteString -> Either String Base-getBase h-      | startWiths h ["1114", "1220", "1340", "1440", "1530", "1620", "171c", "4040", "4120"] = Right Base16-      | startWiths h ["5d", "Qm", "8V", "8t", "G9", "W1", "5d", "S2", "2U"] = Right Base58-      | startWiths h ["ER", "Ei", "E0", "FE", "FT", "Fi", "Fx", "QE", "QS"] = Right Base64-      | otherwise = Left "Unable to infer an encoding"-      where startWiths h = any (`BS.isPrefixOf` h)+getBase h = if len == 0 +  then +    Left "Unable to infer an encoding" +  else +    pure $ [Base16, Base32, Base58, Base64] !! head bsi+  where+    len = Prelude.length bsi+    bsi = elemIndices 0 $ map (unmatch h) [b16Alphabet, b32Alphabet, b58Alphabet, b64Alphabet]+    unmatch str alphabet = BS.length $ BS.filter (`BS.notElem` alphabet) str +    b16Alphabet :: BS.ByteString+    b16Alphabet = "0123456789abcdef"++    -- from RFC https://tools.ietf.org/rfc/rfc4648.txt+    -- same one used by Hasekll's `memory` for the encoding/decoding+    b32Alphabet :: BS.ByteString+    b32Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567="++    b58Alphabet :: BS.ByteString+    b58Alphabet = B58.unAlphabet B58.bitcoinAlphabet++    b64Alphabet :: BS.ByteString+    b64Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="++-- TODO: test this with quickcheck++-- old implementation+-- getBase :: BS.ByteString -> Either String Base+-- getBase h+--       | startWiths h ["1114", "1220", "1340", "1440", "1530", "1620", "171c", "4040", "4120"] = Right Base16+--       | startWiths h ["5d", "Qm", "8V", "8t", "G9", "W1", "5d", "S2", "2U"] = Right Base58+--       | startWiths h ["ER", "Ei", "E0", "FE", "FT", "Fi", "Fx", "QE", "QS"] = Right Base64+--       | otherwise = Left "Unable to infer an encoding"+--       where startWiths h = any (`BS.isPrefixOf` h)++ -- | Encode the binary data using a given 'Base'. encoder :: ByteArrayAccess a => Base -> a -> Either String BA.Bytes encoder base bs = case base of             Base2  -> return $ BA.convert bs             Base16 -> return $ BE.convertToBase BE.Base16 bs-            Base32 -> Left "Base32 encoder not implemented"+            Base32 -> return $ BE.convertToBase BE.Base32 bs             Base58 -> return $ BA.convert $ B58.encodeBase58 B58.bitcoinAlphabet                                                               (BA.convert bs :: BS.ByteString)             Base64 -> return $ BE.convertToBase BE.Base64 bs
src/Crypto/Multihash/Weak.hs view
@@ -41,9 +41,12 @@   , weakMultihash'   , weakMultihashlazy   , weakMultihashlazy'+  , truncatedWeakMultihash+  , truncatedWeakMultihash'   , toWeakMultihash   , checkWeakMultihash   , checkWeakMultihash'+  , getBase   ) where  import Crypto.Hash (Digest, hashWith, hashlazy)@@ -212,6 +215,25 @@ weakMultihashlazy' :: ByteString -> BL.ByteString -> WeakMultihashDigest weakMultihashlazy' alg p = eitherToErr $ weakMultihashlazy alg p +-- | Helper to multihash a 'ByteArrayAccess' using a supported hash algorithm. +--   Uses 'Crypto.Hash.hash' for hashing and truncates the hash to the lenght +--   specified (must be positive and not longer than the digest length).+truncatedWeakMultihash :: ByteArrayAccess bs +                       => Int -> ByteString -> bs -> Either String WeakMultihashDigest+truncatedWeakMultihash len alg bs = do+    WeakMultihashDigest alg' len' digest <- weakMultihash alg bs+    if len <= 0 || len > len'+      then Left "invalid truncated multihash lenght"+      else Right $ WeakMultihashDigest alg' len (BA.take len digest)++-- | Unsafe helper to multihash a 'ByteArrayAccess' using a supported hash algorithm. +--   Uses 'Crypto.Hash.hash' for hashing and truncates the hash to the lenght +--   specified (must be positive and not longer than the digest length, otherwise+--   the function will throw an error).+truncatedWeakMultihash' :: ByteArrayAccess bs +                       => Int -> ByteString -> bs -> WeakMultihashDigest+truncatedWeakMultihash' len alg bs = eitherToErr $ truncatedWeakMultihash len alg bs+ -------------------------------------------------------------------------------  -- | Imports a multihash into a 'WeahMultihashDigest' if possible.@@ -242,12 +264,14 @@  ------------------------------------------------------------------------------- --- | 'checkPayload' wrapper for API retro-compatibility.+-- | Safely check the correctness of an encoded 'Encodable' against the +--   corresponding data. checkWeakMultihash :: (IsString s, ConvertibleStrings s BS.ByteString, ByteArrayAccess bs)                   => s -> bs -> Either String Bool checkWeakMultihash h p = checkPayload h (Payload p) --- | Unsafe 'checkPayload' wrapper for API retro-compatibility.+-- | Unsafe version of 'checkWeakMultihash'. +--   Throws on encoding/decoding errors instead of returning an 'Either' type. checkWeakMultihash' :: (IsString s, ConvertibleStrings s BS.ByteString, ByteArrayAccess bs)                    => s -> bs -> Bool checkWeakMultihash' h p = checkPayload' h (Payload p)
test/Spec.hs view
@@ -1,20 +1,65 @@ {-# LANGUAGE OverloadedStrings #-} -import Control.Monad (zipWithM)+import Control.Monad (zipWithM, forM_) import Crypto.Multihash import Crypto.Multihash.Weak (weakMultihash, checkWeakMultihash, toWeakMultihash)-import Data.ByteString (ByteString, pack)-import Test.Hspec+import Data.ByteString (ByteString, pack, unpack) import Text.Printf (printf) +import Test.Hspec+import Test.QuickCheck++-- TODO: +--   * use QuickCheck to test Multihash serializatio/deserializatio/check properties+--     especially now that we infer the decoding for arbitrary truncations+--   * test for valid and invalid truncated multihashes++instance Arbitrary ByteString where+    arbitrary = pack `fmap` arbitrary+    --coarbitrary = coarbitrary . unpack+ testString :: ByteString testString = "test"  failTestString :: ByteString failTestString = "test1" +weakAlgos = [ "sha1", "sha256", "sha512", "sha3-512" +            , "sha3-384", "sha3-256", "sha3-224"+            , "blake2b-512", "blake2s-256" ]++-- Useful for testing, generated in Weak by+-- tester = map (\(a,_) -> _getLength $ weakMultihash' a ("test"::ByteString)) allowedAlgos+maxHashLengths = [20,32,64,64,48,32,28,64,32]+ main :: IO ()-main = hspec $ do+main = runSpec++prop_gen_check :: ByteString -> Property+prop_gen_check str = +  let m = multihash SHA1 str+      enc :: ByteString+      enc = encode' Base16 m +  in check' enc m === True++prop_get_base :: Base -> ByteString -> Property+prop_get_base base str = +  let m = multihash SHA1 str+      enc :: ByteString+      enc = encode' base m +  in getBase enc === Right base++runQC = quickCheck prop_gen_check++runSpec = hspec $ do+  ------------------------------------------------------------------------+  describe "Multihash: check properties with QuickCheck" $ do+    it "correctly encodes and checks SHA1" $+      property prop_gen_check+    forM_ [Base16, Base32, Base58, Base64] $ \b -> +      it ("correctly infer" ++ show b ++ "encodings on full-length hashes") $ +        property (prop_get_base b)+  ------------------------------------------------------------------------   mhEncoding SHA1 h1   mhCheck mh checkMultihash SHA1 h1   mhEncoding SHA256 h2@@ -33,46 +78,49 @@   mhCheck mh checkMultihash Blake2b_512 h8   mhEncoding Blake2s_256 h9   mhCheck mh checkMultihash Blake2s_256 h9-+  ------------------------------------------------------------------------   traverse (uncurry wmhEncoding)             (zip weakAlgos h)   traverse (uncurry (mhCheck wmh checkWeakMultihash))            (zip weakAlgos h)-+  ------------------------------------------------------------------------   describe "Multihash: fails correctly when" $ do-    it "checking a truncated multihash" $+    it "checking an invalid truncated multihash" $       checkMultihash ("1340ee26b0dd4af7e749aa1a8e"::ByteString) testString          `shouldBe` Left "Corrupted MultihasDigest: invalid length"-    it "checking an invalid multihash" $+    it "checking an invalid truncated multihash" $       checkMultihash ("dd4af7e749aa1a8e1340ee26b0"::ByteString) testString -        `shouldBe` Left "Unable to infer an encoding"-+        `shouldBe` Left "Corrupted MultihasDigest: invalid length"+     describe "Weak Multihash: fails correctly when" $ do-    it "checking a truncated multihash" $+    it "checking an invalid truncated multihash" $       checkWeakMultihash ("1340ee26b0dd4af7e749aa1a8e"::ByteString) testString          `shouldBe` Left "Corrupted MultihasDigest: invalid length"-    it "checking an invalid multihash" $+    it "checking an invalid truncated multihash" $       checkWeakMultihash ("dd4af7e749aa1a8e1340ee26b0"::ByteString) testString -        `shouldBe` Left "Unable to infer an encoding"+        `shouldBe` Left "Corrupted MultihasDigest: invalid length"+  ------------------------------------------------------------------------   where     mh = "Multihash"::String     wmh = "Weak Multihash"::String      mhEncoding :: (HashAlgorithm a, Codable a, Show a) => a -                      -> (ByteString, ByteString, ByteString) -> SpecWith ()-    mhEncoding alg (sm16, sm58, sm64) = +                      -> (ByteString, ByteString, ByteString, ByteString) -> SpecWith ()+    mhEncoding alg (sm16, sm32, sm58, sm64) =          let m = multihash alg testString in         describe (printf "Multihash: encoding %s multihash" (show alg)) $ do           it "returns the correct Base16 hash" $              encode' Base16 m `shouldBe` sm16+          it "returns the correct Base32 hash" $ +            encode' Base32 m `shouldBe` sm32           it "returns the correct Base58 hash" $              encode' Base58 m `shouldBe` sm58           it "returns the correct Base64 hash" $              encode' Base64 m `shouldBe` sm64      wmhEncoding :: ByteString -                       -> (ByteString, ByteString, ByteString) -> SpecWith ()-    wmhEncoding alg (sm16, sm58, sm64) = +                       -> (ByteString, ByteString, ByteString, ByteString) -> SpecWith ()+    wmhEncoding alg (sm16, sm32, sm58, sm64) =          let m = case weakMultihash alg testString of                    Right val -> val                   Left  err -> error err@@ -80,6 +128,8 @@         describe (printf "Weak Multihash: encoding %s multihash" (show alg)) $ do           it "returns the correct Base16 hash" $              encode' Base16 m `shouldBe` sm16+          it "returns the correct Base32 hash" $ +            encode' Base32 m `shouldBe` sm32           it "returns the correct Base58 hash" $              encode' Base58 m `shouldBe` sm58           it "returns the correct Base64 hash" $ @@ -88,19 +138,25 @@         describe (printf "Weak Multihash: decoding %s multihash" (show alg)) $ do           it "imports the correct hash from Base16" $              toWeakMultihash sm16 `shouldBe` Right m+          it "imports the correct hash from Base32" $ +            toWeakMultihash sm32 `shouldBe` Right m           it "imports the correct hash from Base58" $              toWeakMultihash sm58 `shouldBe` Right m           it "imports the correct hash from Base64" $              toWeakMultihash sm64 `shouldBe` Right m          -- hashChecker :: (HashAlgorithm a, Codable a, Show a) => a -    --                -> (ByteString, ByteString, ByteString) -> SpecWith ()-    mhCheck t checker alg (e16, e58, e64) =+    --                -> (ByteString, ByteString, ByteString, ByteString) -> SpecWith ()+    mhCheck t checker alg (e16, e32, e58, e64) =       describe (printf "%s: using checkPayload on %s hashes" t (show alg)) $ do         it "checks correctly Base16 hashes"  $           checker e16 testString `shouldBe` Right True         it "fails correctly on Base16 hashes" $           checker e16 failTestString `shouldBe` Right False+        it "checks correctly Base32 hashes"  $+          checker e32 testString `shouldBe` Right True+        it "fails correctly on Base32 hashes" $+          checker e32 failTestString `shouldBe` Right False         it "checks correctly Base58 hashes" $           checker e58 testString `shouldBe` Right True         it "fails correctly on Base58 hashes" $@@ -110,39 +166,44 @@         it "fails correctly on Base64 hashes" $           checker e64 failTestString `shouldBe` Right False -    weakAlgos = [ "sha1", "sha256", "sha512", "sha3-512" -                , "sha3-384", "sha3-256", "sha3-224"-                , "blake2b-512", "blake2s-256" ]-     -- array of triples of hashes of the string "test"     h@[h1, h2, h3, h4, h5, h6, h7, h8, h9] =        [         ( "1114a94a8fe5ccb19ba61c4c0873d391e987982fbbd3"+        , "CEKKSSUP4XGLDG5GDRGAQ46TSHUYPGBPXPJQ===="         , "5dt9CqvXK9qs7vazf7k7ZRqe28VPTg"         , "ERSpSo/lzLGbphxMCHPTkemHmC+70w==")       , ( "12209f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"+        , "CIQJ7BWQQGEEY7LFTIX6VIGFLLIBLI57J4NSWC4CFTIV23AVWDYAUCA="          , "QmZ5NmGeStdit7tV6gdak1F8FyZhPsfA843YS9f2ywKH6w"         , "EiCfhtCBiEx9ZZov6qDFWtAVo79PGysLgizRXWwVsPAKCA==")       , ( "1340ee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5fa9ad8e6f57f50028a8ff"+        , "CNAO4JVQ3VFPPZ2JVINI5Y6BBLUZEP3BRGAHOLSHH6EBTJOUSQHA3MT2YGC7RIHB2X4E7CF4RB75M6YUG4ZMGBGML6U23DTPK72QAKFI74======"         , "8VxYhLL7s5BvLogtUGgNJ7DebZ5Ba9mG3izfc7v6o4RZ28x469vJaifa3TQ13Z9DycmAuJWnp7ErkZofm4rsMo78fQ"         , "E0DuJrDdSvfnSaoajuPBCumSP2GJgHcuRz+IGaXUlA4NsnrBhfig4dX4T4i8iH/WexQ3MsMEzF+prY5vV/UAKKj/")       , ( "14409ece086e9bac491fac5c1d1046ca11d737b92a2b2ebd93f005d7b710110c0a678288166e7fbe796883a4f2e9b3ca9f484f521d0ce464345cc1aec96779149c14"+        , "CRAJ5TQIN2N2YSI7VROB2ECGZII5ON5ZFIVS5PMT6AC5PNYQCEGAUZ4CRALG4756PFUIHJHS5GZ4VH2IJ5JB2DHEMQ2FZQNOZFTXSFE4CQ======"         , "8tXEcJyq2MCx27UHYbZxmte37ezBawV35QhfKPtq5QeSnX66q4DDf1cwMYUh2pUVbxdQgrDaSjbrPrfNxzvSSLQAtT"         , "FECezghum6xJH6xcHRBGyhHXN7kqKy69k/AF17cQEQwKZ4KIFm5/vnlog6Ty6bPKn0hPUh0M5GQ0XMGuyWd5FJwU")       , ( "1530e516dabb23b6e30026863543282780a3ae0dccf05551cf0295178d7ff0f1b41eecb9db3ff219007c4e097260d58621bd"+        , "CUYOKFW2XMR3NYYAE2DDKQZIE6AKHLQNZTYFKUOPAKKRPDL76DY3IHXMXHNT74QZAB6E4CLSMDKYMIN5"         , "G9LerVN7c3uUAAymoAGkCGZPn53PZi1SHwPJ2nznLp82jcM2M1KLwpfrZh3F1QRVG3f2"         , "FTDlFtq7I7bjACaGNUMoJ4Cjrg3M8FVRzwKVF41/8PG0Huy52z/yGQB8TglyYNWGIb0=")       , ( "162036f028580bb02cc8272a9a020f4200e346e276ae664e45ee80745574e2f5ab80"+        , "CYQDN4BILAF3ALGIE4VJUAQPIIAOGRXCO2XGMTSF52AHIVLU4L22XAA="         , "W1d9SeHn1mCnY3jZMs5YeqfFbwEnq5gQy1VDymGoPK28RD"         , "FiA28ChYC7AsyCcqmgIPQgDjRuJ2rmZORe6AdFV04vWrgA==")       , ( "171c3797bf0afbbfca4a7bbba7602a2b552746876517a7f9b7ce2db0ae7b"+        , "C4ODPF57BL537SSKPO52OYBKFNKSORUHMUL2P6NXZYW3BLT3"         , "5daZNVMeTfSuCvu7rBKsFkzEMebnuGjNpos1ThF1c"         , "Fxw3l78K+7/KSnu7p2AqK1UnRodlF6f5t84tsK57")       , ( "4040a71079d42853dea26e453004338670a53814b78137ffbed07603a41d76a483aa9bc33b582f77d30a65e6f29a896c0411f38312e1d66e0bf16386c86a89bea572"+        , "IBAKOEDZ2QUFHXVCNZCTABBTQZYKKOAUW6ATP7562B3AHJA5O2SIHKU3YM5VQL3X2MFGLZXSTKEWYBAR6OBRFYOWNYF7CY4GZBVITPVFOI======"         , "S2XUqUDxz3MHMZtJpCZKt5oRjXHQ34gsyDBT759qNwoSP9rDBHVHxjQUQtXfExotxTqf4rMEXQkNmXE3N9mhoZX6wK"         , "QECnEHnUKFPeom5FMAQzhnClOBS3gTf/vtB2A6QddqSDqpvDO1gvd9MKZebymolsBBHzgxLh1m4L8WOGyGqJvqVy")       , ( "4120f308fc02ce9172ad02a7d75800ecfc027109bc67987ea32aba9b8dcc7b10150e"+        , "IEQPGCH4ALHJC4VNAKT5OWAA5T6AE4IJXRTZQ7VDFK5JXDOMPMIBKDQ="         , "2UPuEK7FVakwP3yUak5jKQhZb6pgpbcqYoRZ2tDzgeCfVr5"         , "QSDzCPwCzpFyrQKn11gA7PwCcQm8Z5h+oyq6m43MexAVDg==")       ]-  +