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 +7/−7
- app/Main.hs +2/−2
- crypto-multihash.cabal +2/−1
- src/Crypto/Multihash.hs +38/−10
- src/Crypto/Multihash/Internal.hs +42/−10
- src/Crypto/Multihash/Weak.hs +26/−2
- test/Spec.hs +85/−24
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==") ]- +