packages feed

longshot (empty) → 0.1.0.0

raw patch · 9 files changed

+664/−0 lines, 9 filesdep +basedep +base16-bytestringdep +blake2

Dependencies added: base, base16-bytestring, blake2, blake3, bytestring, containers, cryptohash-sha256, cryptonite, deepseq, docopt, longshot, memory, parallel, tasty, tasty-hunit, tasty-quickcheck, template-haskell

Files

+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Francis Lim (c) 2020++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Francis Lim nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,122 @@+<p align="center"> <img src="longshot.png" height="200"/> </p>++> Is it really a __long shot__ to hope that the COVID-19 pandemic will end?++++[![Build Status](https://travis-ci.com/thyeem/longshot.svg?branch=master)](https://travis-ci.com/thyeem/longshot) <!-- [![Hackage](https://img.shields.io/hackage/v/longshot)](https://hackage.haskell.org/package/longshot) -->++# longshot++__Search for preimage__ from a given hash value using _Brute-force_ method based on _parallelism_++* Support various __search lengths__, __character sets__ and __hashers__.+* Strict mode: searches only for a given _exact length_+* Deep mode: searches _everything less than or equal_ to a given length.+* Use `CPUs` as _much_ as possible. __Get the most out of them!__+* Use, however, `memory` as _little_ as possible.+  ++Enjoy `longshot`. Good luck!++++```plain+longshot - Fast and concise Brute-force search++Usage:+  longshot run        [-n SIZE] [-c CHARS] [-a HASHER] [--deep] HEX+  longshot image      [-a HASHER] KEY++Commands:+  run                 Brute-force search with given hexstring and options+  image               Generate image from given key string and hash algorithm++Arguments:+  HEX                 Specify target hexstring to search+  KEY                 Specify key string as a preimage++Options:+  -h --help           Show this+  -n SIZE             Specify search length  [default: 8]   +  -c CHARS            Specify characters available in preimage  [default: 0123456789]+  -a HASHER           Specify hash algorithm  [default: sha256]+                      HASHER available below:+                      md5           sha1          ripemd160     whirlpool+                      sha256        sha3_256      sha3_384      sha3_512+                      blake2s_256   blake2b_256   blake2b_384   blake2b_512+                      blake3_256    blake3_384    blake3_512+                      keccak_256    keccak_384    keccak_512+                      skein_256     skein_384     skein_512+  --deep              Search deeply including less than a given search length+```++## How to build+```bash+$ git clone https://github.com/thyeem/longshot.git++$ make build ++## Optional: test if installed properly using quickcheck+$ make test++## You can see an executable 'longshot' here+$ cd app+```++## Quick start+```bash+## Refer to the following usage:+## Note that the order of arguments and options matter.+$ ./longshot -h++## Generate test hash values+$ ./longshot image 12345+5994471abb01112afcc18159f6cc74b4f511b99806da59b3caf5a9c173cacfc5++$ ./longshot image 12345678+ef797c8118f02dfb649607dd5d3f8c7623048c9c063d532cc95c5ed7a898a64f++## Brute-force search with default options:+## The same as: ./longshot -n 8 -c 0123456789 -a sha256 ef797c..98a64f+$ ./longshot run ef797c8118f02dfb649607dd5d3f8c7623048c9c063d532cc95c5ed7a898a64f+Found  12345678++## Search below fails because default search length is 8.+## In default mode, it searches by the exact length of key.+$ ./longshot run 5994471abb01112afcc18159f6cc74b4f511b99806da59b3caf5a9c173cacfc5+Not found++## If you don't know the key length, use deep search. (--deep)+## It will try to search with less than or equal to the given length.+$ ./longshot run --deep 5994471abb01112afcc18159f6cc74b4f511b99806da59b3caf5a9c173cacfc5+Found  12345+```+These are all about how to use `longshot`.  +See below for more interesting detailed examples.++## More examples+```bash+## Generate a example image using Blake2b hash algorithm+## Blake2b-hashed sofia (my first daughter!)+$ ./longshot image -a blake2b sofia+bb40f637bb211532318965627f15bb165f701230fd83e0adf8cd673a6ee02830++## Need different character set. Don't forget --deep+$ ./longshot run -c 'abcdefghijklmnopqrstuvwxyz' -a blake2b --deep bb40f6..e02830+Found  sofia++## You should consider the following if there might be more characters in preimage.+## More characters, much longer time to find!+$ ./longshot run -c 'abcdefghijklmnopqrstuvwxyz0123456789' -a blake2b --deep bb40f6..e02830++## Roughly, time spending is proportional to (Number of char available) ^ (char length).+## Exponent 'char length' is surely more dominant! Use long-long password as always!++## longshot is very efficient and get the most of CPU's power in parallel.+## But this kind of work would be very painful time even for longshot.+$ ./longshot run -n 12 -c 'abcdefghijklmnopqrstuvwxyz0123456789`~!@#$%^&*()-=_+[]{}\|' \+                 -a blake2b --deep bb40f6..e02830 +RTS -s++## '+RTS -s' the end of line is optional, and that is for a summary of CPU time and memory.+```
+ app/Main.hs view
@@ -0,0 +1,73 @@+{-# LANGUAGE QuasiQuotes #-}+module Main where++import           Control.Monad+import           System.Environment+import           System.Console.Docopt+import qualified Data.ByteString.Char8         as C+import qualified Data.ByteString.Base16        as H+import           Crypto.Longshot.Internal+import           Crypto.Longshot.Hasher++patterns :: Docopt+patterns = [docopt|+longshot - Fast and concise Brute-force search++Usage:+  longshot run        [-n SIZE] [-c CHARS] [-a HASHER] [--deep] HEX+  longshot image      [-a HASHER] KEY++Commands:+  run                 Brute-force search with given hexstring and options+  image               Generate image from given key string and hash algorithm++Arguments:+  HEX                 Specify target hexstring to search+  KEY                 Specify key string as a preimage++Options:+  -h --help           Show this+  -n SIZE             Specify search length  [default: 8]   +  -c CHARS            Specify characters in preimage  [default: 0123456789]+  -a HASHER           Specify hash algorithm  [default: sha256]+                      HASHER available below:+                      md5           sha1          ripemd160     whirlpool+                      sha256        sha3_256      sha3_384      sha3_512+                      blake2s_256   blake2b_256   blake2b_384   blake2b_512+                      blake3_256    blake3_384    blake3_512+                      keccak_256    keccak_384    keccak_512+                      skein_256     skein_384     skein_512+  --deep              Search deeply including less than a given search length+|]++-- | Defines args-ops frequently used+(><) = isPresent+(<->|!) = getArgOrExitWith patterns+(<->) = getArg++main :: IO ()+main = do+  args <- parseArgsOrExit patterns =<< getArgs+  when (args >< command "image") $ genImage args+  when (args >< command "run") $ run args++-- | Command: image+genImage :: Arguments -> IO ()+genImage args = do+  key    <- C.pack <$> (args <->|! argument "KEY")+  hasher <- getHasher <$> (args <->|! shortOption 'a')+  putStrLn . C.unpack . H.encode . hasher $ key++-- | Command: run+run :: Arguments -> IO ()+run args = do+  hex    <- args <->|! argument "HEX"+  chars  <- args <->|! shortOption 'c'+  size   <- (read <$> (args <->|! shortOption 'n')) :: IO Int+  hasher <- getHasher <$> (args <->|! shortOption 'a')+  let solver | args >< longOption "deep" = bruteforceDeep+             | otherwise                 = bruteforce+  let found = solver size chars hex hasher+  case found of+    Just key -> putStrLn $ "Found  " <> key+    _        -> putStrLn "Not found"
+ longshot.cabal view
@@ -0,0 +1,119 @@+cabal-version: 1.12++-- This file has been generated from package.yaml by hpack version 0.33.0.+--+-- see: https://github.com/sol/hpack+--+-- hash: 5c862a15f456f6ba99cb05a6abe2c68fa81755d8855575d2dd63872d01d2dd7c++name:           longshot+version:        0.1.0.0+synopsis:       Fast Brute-force search using parallelism+description:    Longshot enables to search for preimages from a given hash value+                using a brute-force method based on parallelism.+                .+                * Support various search lengths, character sets and hashers+                * Strict mode: searches only for a given exact length+                * Deep mode: searches everything less than or equal to a given length.+                * Use @CPUs@ as much as possible. Get the most out of them!+                * Use, however, @memory@ as little as possible.+                .+                Please see the documentation at https://github.com/thyeem/longshot+                for usage example.+                .+category:       algorithm, search, cryptography, parallelism+homepage:       https://github.com/thyeem/longshot#readme+bug-reports:    https://github.com/thyeem/longshot/issues+author:         Francis Lim, Jongwhan Lee+maintainer:     thyeem@gmail.com+copyright:      Francis, 2020+license:        MIT+license-file:   LICENSE+build-type:     Simple+extra-source-files:+    README.md++source-repository head+  type: git+  location: https://github.com/thyeem/longshot++library+  exposed-modules:+      Crypto.Longshot+      Crypto.Longshot.Hasher+      Crypto.Longshot.Internal+      Crypto.Longshot.TH+  other-modules:+      Paths_longshot+  hs-source-dirs:+      src+  default-extensions: TemplateHaskell KindSignatures DataKinds+  build-depends:+      base >=4.12.0 && <5+    , base16-bytestring >=0.1.0 && <0.2+    , blake2 >=0.3.0 && <0.4+    , blake3 >=0.2 && <0.3+    , bytestring >=0.10.8 && <0.11+    , containers >=0.6 && <0.7+    , cryptohash-sha256 >=0.11.101 && <0.12+    , cryptonite >=0.25+    , deepseq >=1.4.4 && <1.5+    , docopt >=0.7.0 && <0.8+    , memory >=0.14.0 && <0.16+    , parallel >=3.2.2 && <3.3+    , template-haskell >=2.14.0 && <2.17+  default-language: Haskell2010++executable longshot+  main-is: Main.hs+  other-modules:+      Paths_longshot+  hs-source-dirs:+      app+  default-extensions: TemplateHaskell KindSignatures DataKinds+  ghc-options: -threaded -rtsopts -with-rtsopts=-N -dynamic -feager-blackholing -g0 -eventlog+  build-depends:+      base >=4.12.0 && <5+    , base16-bytestring >=0.1.0 && <0.2+    , blake2 >=0.3.0 && <0.4+    , blake3 >=0.2 && <0.3+    , bytestring >=0.10.8 && <0.11+    , containers >=0.6 && <0.7+    , cryptohash-sha256 >=0.11.101 && <0.12+    , cryptonite >=0.25+    , deepseq >=1.4.4 && <1.5+    , docopt >=0.7.0 && <0.8+    , longshot+    , memory >=0.14.0 && <0.16+    , parallel >=3.2.2 && <3.3+    , template-haskell >=2.14.0 && <2.17+  default-language: Haskell2010++test-suite longshot-test+  type: exitcode-stdio-1.0+  main-is: Spec.hs+  other-modules:+      Paths_longshot+  hs-source-dirs:+      test+  default-extensions: TemplateHaskell KindSignatures DataKinds+  ghc-options: -threaded -rtsopts -with-rtsopts=-N+  build-depends:+      base >=4.12.0 && <5+    , base16-bytestring >=0.1.0 && <0.2+    , blake2 >=0.3.0 && <0.4+    , blake3 >=0.2 && <0.3+    , bytestring >=0.10.8 && <0.11+    , containers >=0.6 && <0.7+    , cryptohash-sha256 >=0.11.101 && <0.12+    , cryptonite >=0.25+    , deepseq >=1.4.4 && <1.5+    , docopt >=0.7.0 && <0.8+    , longshot+    , memory >=0.14.0 && <0.16+    , parallel >=3.2.2 && <3.3+    , tasty+    , tasty-hunit+    , tasty-quickcheck+    , template-haskell >=2.14.0 && <2.17+  default-language: Haskell2010
+ src/Crypto/Longshot.hs view
@@ -0,0 +1,70 @@+-- |+-- Module      : Crypto.Longshot+-- License     : MIT+-- Maintainer  : Francis Lim <thyeem@gmail.com>+-- Stability   : experimental+-- Portability : unknown+--+-- How big is the search space? The space consists of two axes.+--+-- * X-axis: Number of characters available+-- * Y-axis: Search length of preimage to find+--+-- Note that it's proportional to @(X ^ Y)@ rather than @(X * Y)@+--+-- The values below are defined by default.+--+-- When not provided as options in CUI, the following values are used.+--+module Crypto.Longshot+  ( defChars+  , defSearchLength+  , defNumPrefix+  , defNumBind+  , image+  , byteChars+  , bytePrefixes+  , toKey+  )+where++import           Control.Monad+import qualified Data.ByteString.Char8         as C+import qualified Data.ByteString.Base16        as H++-- | Characters available in a preimage+defChars :: String+defChars = "0123456789"++-- | Search length of preimage+defSearchLength :: Int+defSearchLength = 8++-- | Limit search length of preimage+limitSearchLength :: Int+limitSearchLength = 20++-- | Value related to the number of sparks+defNumPrefix :: Int+defNumPrefix = 3++-- | Number of actions in TH bruteforceN+defNumBind :: Int+defNumBind = limitSearchLength - defNumPrefix+--------------------------------------------------------------------++-- | Image bytestring: target hash value to find+image :: String -> C.ByteString+image = fst . H.decode . C.pack++-- | Bytestring usable for preimage+byteChars :: String -> [C.ByteString]+byteChars chars = C.pack . (: []) <$> chars++-- | Combination of prefixes possible: size of (length of chars) ^ (numPrefix)+bytePrefixes :: Int -> String -> [C.ByteString]+bytePrefixes numPrefix chars = C.pack <$> replicateM numPrefix chars++-- | Convert preimage found into key string+toKey :: C.ByteString -> String+toKey = C.unpack
+ src/Crypto/Longshot/Hasher.hs view
@@ -0,0 +1,55 @@+-- |+-- Module      : Crypto.Longshot.Hasher+-- License     : MIT+-- Maintainer  : Francis Lim <thyeem@gmail.com>+-- Stability   : experimental+-- Portability : unknown+--+module Crypto.Longshot.Hasher+  ( Hasher+  , getHasher+  )+where++import           GHC.TypeLits+import           Numeric.Natural+import qualified Data.ByteArray                as B+import qualified Data.ByteString.Char8         as C+import qualified Crypto.Hash                   as X+import qualified Crypto.Hash.SHA256            as S+import qualified Crypto.Hash.BLAKE2.BLAKE2s    as B2s+import qualified Crypto.Hash.BLAKE2.BLAKE2b    as B2b+import qualified BLAKE3                        as B3++-- | Type for hash functions available+type Hasher = C.ByteString -> C.ByteString++type Blake3_256 = C.ByteString -> B3.Digest (32 :: Nat)+type Blake3_384 = C.ByteString -> B3.Digest (48 :: Nat)+type Blake3_512 = C.ByteString -> B3.Digest (64 :: Nat)++-- | Select hasher by defined name+getHasher :: String -> Hasher+getHasher name = case name of+  "md5"         -> B.convert . X.hashWith X.MD5+  "sha1"        -> B.convert . X.hashWith X.SHA1+  "ripemd160"   -> B.convert . X.hashWith X.RIPEMD160+  "whirlpool"   -> B.convert . X.hashWith X.Whirlpool+  "sha256"      -> S.hash+  "sha3_256"    -> B.convert . X.hashWith X.SHA3_256+  "sha3_384"    -> B.convert . X.hashWith X.SHA3_384+  "sha3_512"    -> B.convert . X.hashWith X.SHA3_512+  "blake2s_256" -> B2s.hash 32 mempty+  "blake2b_256" -> B2b.hash 32 mempty+  "blake2b_384" -> B2b.hash 48 mempty+  "blake2b_512" -> B2b.hash 64 mempty+  "blake3_256"  -> B.convert . (B3.hash . (: []) :: Blake3_256)+  "blake3_384"  -> B.convert . (B3.hash . (: []) :: Blake3_384)+  "blake3_512"  -> B.convert . (B3.hash . (: []) :: Blake3_512)+  "keccak_256"  -> B.convert . X.hashWith X.Keccak_256+  "keccak_384"  -> B.convert . X.hashWith X.Keccak_384+  "keccak_512"  -> B.convert . X.hashWith X.Keccak_512+  "skein_256"   -> B.convert . X.hashWith X.Skein256_256+  "skein_384"   -> B.convert . X.hashWith X.Skein512_384+  "skein_512"   -> B.convert . X.hashWith X.Skein512_512+  a             -> errorWithoutStackTrace $ "Not allowed hash algorithm  " <> a
+ src/Crypto/Longshot/Internal.hs view
@@ -0,0 +1,62 @@+-- |+-- Module      : Crypto.Longshot.Internal+-- License     : MIT+-- Maintainer  : Francis Lim <thyeem@gmail.com>+-- Stability   : experimental+-- Portability : unknown+--+module Crypto.Longshot.Internal+  ( bruteforce+  , bruteforceDeep+  )+where++import           Control.Applicative+import           Control.DeepSeq+import           Control.Parallel+import           Data.Foldable+import qualified Data.ByteString.Char8         as C+import           Language.Haskell.TH+import           Crypto.Longshot+import           Crypto.Longshot.TH+import           Crypto.Longshot.Hasher++-- Declaration of bruteforceN: generating code by splicing+$( funcGenerator )++-- | Brute-force search only for a given exact length+bruteforce :: Int -> String -> String -> Hasher -> Maybe String+bruteforce size chars hex hasher = found+ where+  found  = foldl' (<|>) empty (runPar <%> prefixes)+  runPar = bruteforcePar numBind (byteChars chars) (image hex) hasher+  numPrefix | size < defNumPrefix = 1+            | otherwise           = defNumPrefix+  numBind  = size - numPrefix+  prefixes = bytePrefixes numPrefix chars++-- | Pick up an appropriate search function+bruteforcePar+  :: Int+  -> [C.ByteString]+  -> C.ByteString+  -> Hasher+  -> C.ByteString+  -> Maybe String+bruteforcePar n+  | n `elem` [0 .. defNumBind] = $( funcList ) !! n+  | otherwise = errorWithoutStackTrace "Not available search length"++-- | Deep Brute-force search including less than a given search size+bruteforceDeep :: Int -> String -> String -> Hasher -> Maybe String+bruteforceDeep size x y z = foldl' (<|>) empty found+ where+  found = deep x y z <%> [1 .. size]+  deep a b c d = bruteforce d a b c++-- | Parallel map using deepseq, par and pseq+(<%>) :: (NFData a, NFData b) => (a -> b) -> [a] -> [b]+f <%> []       = []+f <%> (x : xs) = y `par` ys `pseq` (y : ys) where+  y  = force $ f x+  ys = f <%> xs
+ src/Crypto/Longshot/TH.hs view
@@ -0,0 +1,49 @@+-- |+-- Module      : Crypto.Longshot.TH+-- License     : MIT+-- Maintainer  : Francis Lim <thyeem@gmail.com>+-- Stability   : experimental+-- Portability : unknown+--+module Crypto.Longshot.TH where++import           Control.Monad+import           Data.Foldable+import           Language.Haskell.TH+import           Crypto.Longshot++-- | Brute-force with N-search-length using TH+bruteforceN :: Int -> Q Exp -> Q Exp -> Q Exp -> Q Exp -> Q Exp+bruteforceN numBind chars hex hasher prefix = do+  names <- replicateM numBind (newName "names")+  let pats  = varP <$> names+  let bytes = [| $( prefix ) <>+                 $( foldl' (\a b -> [| $a <> $b |])+                    [| mempty |]+                    (varE <$> names)+                  )+              |]+  let cond  = condE [| $( appE hasher bytes ) == $(hex) |]+                    [| Just (toKey $( bytes )) |]+                    [| Nothing |]+  let stmts = ((`bindS` chars) <$> pats) <> [noBindS cond]+  [| foldl' (<|>) empty $( compE stmts ) |]++-- | Declare functions to run in parallel for search+funcGenerator :: Q [Dec]+funcGenerator = forM [0 .. defNumBind] funcG where +  funcG numBind = do+    let name = mkName $ "bruteforce" <> show numBind+    chars <- newName "chars"+    hex <- newName "hex"+    hasher <- newName "hasher"+    prefix <- newName "prefix"+    funD name +      [ clause [varP chars, varP hex, varP hasher, varP prefix] +        (normalB (bruteforceN numBind (varE chars) (varE hex) (varE hasher) (varE prefix))) +        []+      ]++-- | Get list of functions to run in parallel for search+funcList :: Q Exp+funcList = listE (varE . mkName . ("bruteforce" <>) . show <$> [0 .. defNumBind])
+ test/Spec.hs view
@@ -0,0 +1,84 @@+import           Test.Tasty+import           Test.Tasty.QuickCheck+import           Test.Tasty.HUnit+import           Control.Monad+import qualified Data.ByteString.Char8         as C+import qualified Data.ByteString.Base16        as H+import           Crypto.Longshot.Internal+import           Crypto.Longshot.Hasher++main :: IO ()+main = defaultMain tests++-- | Search Length limit+-- The key length was limited to 4 as it takes a long time to test+limit :: Int+limit = 4++-- | Characters prepared for test+chars :: String+chars = "0123456789abcdef~'*+-%^?[]/"++-- | Hash algorithms available+hashers :: [String]+hashers =+  [ "md5"+  , "sha1"+  , "ripemd160"+  , "whirlpool"+  , "sha256"+  , "sha3_256"+  , "sha3_384"+  , "sha3_512"+  , "blake2s_256"+  , "blake2b_256"+  , "blake2b_384"+  , "blake2b_512"+  , "blake3_256"+  , "blake3_384"+  , "blake3_512"+  , "keccak_256"+  , "keccak_384"+  , "keccak_512"+  , "skein_256"+  , "skein_384"+  , "skein_512"+  ]++-- | Test for strict mode of Brute-force search+testLongshot :: Hasher -> Gen Bool+testLongshot hasher = do+  size <- choose (1, limit) :: Gen Int+  key  <- replicateM size (elements chars :: Gen Char)+  let hex   = C.unpack . H.encode . hasher . C.pack $ key+  let found = bruteforce size chars hex hasher+  case found of+    Just x | x == key -> return True+    _                 -> return False++-- | Test for deep mode of Brute-force search+testLongshotDeep :: Hasher -> Gen Bool+testLongshotDeep hasher = do+  let size = limit+  size' <- choose (1, limit) :: Gen Int+  key   <- replicateM size' (elements chars :: Gen Char)+  let hex   = C.unpack . H.encode . hasher . C.pack $ key+  let found = bruteforceDeep size chars hex hasher+  case found of+    Just x | x == key -> return True+    _                 -> return False++-- | Property test generator+genTestProp :: (Hasher -> Gen Bool) -> TestName -> TestName -> TestTree+genTestProp f desc algo = testProperty (desc <> algo) (f $ getHasher algo)++-- | Main test of properties+tests :: TestTree+tests = testGroup+  "Longshot Tests"+  [ testGroup "Strict-Longshot search"+              (genTestProp testLongshot "testLongshot with " <$> hashers)+  , testGroup+    "Deep-Longshot search"+    (genTestProp testLongshotDeep "testLongshotDeep with " <$> hashers)+  ]