packages feed

cryptoconditions (empty) → 0.1.0.0

raw patch · 8 files changed

+716/−0 lines, 8 filesdep +aesondep +aeson-quickdep +asn1-encodingsetup-changed

Dependencies added: aeson, aeson-quick, asn1-encoding, asn1-parse, asn1-types, base, base16-bytestring, base64-bytestring, bytestring, containers, cryptoconditions, cryptonite, memory, tasty, tasty-hunit, text, transformers

Files

+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Author name here (c) 2017++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 Author name here 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.
+ Network/CryptoConditions.hs view
@@ -0,0 +1,107 @@+--------------------------------------------------------------------------------+-- Crypto Conditions Standard API+--+-- The Condition type defined in this module supports the standard+-- condition types, library authors wishing to extend CryptoConditions+-- should copy and paste this file into their own project and define their own+-- Condition type.+--------------------------------------------------------------------------------++module Network.CryptoConditions+  ( module CCI+  , Condition(..)+  , ed25519Condition+  , preimageCondition+  , fulfillEd25519+  , readStandardFulfillment+  ) where++import qualified Crypto.PubKey.Ed25519 as Ed2++import Data.ByteString as BS+import Data.Word+import qualified Data.Set as Set++import Network.CryptoConditions.Impl as CCI+++data Condition =+    Preimage Preimage+  | Prefix Prefix Int Condition+  | Threshold Word16 [Condition]+--  Rsa+  | Ed25519 Ed2.PublicKey (Maybe Ed2.Signature)+  | Anon Int Fingerprint Int (Set.Set ConditionType)+  deriving (Show, Eq)+++instance IsCondition Condition where+  getType (Anon 0 _ _ _) = preimageType+  getType (Anon 2 _ _ _) = thresholdType+  getType (Anon 4 _ _ _) = ed25519Type+  getType (Threshold _ _) = thresholdType+  getType (Ed25519 _ _) = ed25519Type+  getType (Preimage _) = preimageType+  getType (Prefix _ _ _) = prefixType++  getCost (Threshold t subs) = thresholdCost t subs+  getCost (Ed25519 _ _) = ed25519Cost+  getCost (Preimage pre) = preimageCost pre+  getCost (Prefix pre mml c) = prefixCost pre mml c+  getCost (Anon _ _ c _) = c++  getFingerprint (Threshold t subs) = thresholdFingerprint t subs+  getFingerprint (Ed25519 pk _) = ed25519Fingerprint pk+  getFingerprint (Preimage pre) = preimageFingerprint pre+  getFingerprint (Prefix pre mml c) = prefixFingerprint pre mml c+  getFingerprint (Anon _ fp _ _) = fp++  getFulfillment (Threshold t subs) = thresholdFulfillment t subs+  getFulfillment (Ed25519 pk msig) = ed25519Fulfillment pk <$> msig+  getFulfillment (Preimage pre) = Just $ preimageFulfillment pre+  getFulfillment (Prefix pre mml c) =  prefixFulfillment pre mml c+  getFulfillment (Anon _ _ _ _) = Nothing++  getSubtypes (Threshold _ sts) = thresholdSubtypes sts+  getSubtypes (Anon _ _ _ sts) = sts+  getSubtypes (Prefix _ _ c)     = prefixSubtypes c+  getSubtypes _                = mempty++  parseFulfillment 0 = parsePreimage Preimage+  parseFulfillment 1 = parsePrefix Prefix+  parseFulfillment 2 = parseThreshold Threshold+  parseFulfillment 4 = parseEd25519 (\a b -> Ed25519 a (Just b))++  verifyMessage (Preimage image) = verifyPreimage image+  verifyMessage (Prefix pre mml cond) = verifyPrefix pre mml cond+  verifyMessage (Threshold m subs) = verifyThreshold m subs+  verifyMessage (Ed25519 pk (Just sig)) = verifyEd25519 pk sig+  verifyMessage _ = const False++  anon t f c = Anon t f c . toConditionTypes+++toConditionTypes :: Set.Set Int -> Set.Set ConditionType+toConditionTypes = Set.map $+  let u = undefined in (\tid -> getType $ Anon tid u u u)+++preimageCondition :: BS.ByteString -> Condition+preimageCondition = Preimage+++ed25519Condition :: Ed2.PublicKey -> Condition+ed25519Condition pk = Ed25519 pk Nothing+++fulfillEd25519 :: Ed2.PublicKey -> Ed2.Signature+               -> Condition -> Condition+fulfillEd25519 pk sig (Threshold t subs) =+  Threshold t $ fulfillEd25519 pk sig <$> subs+fulfillEd25519 pk sig e@(Ed25519 pk' Nothing) =+  if pk == pk' then Ed25519 pk (Just sig) else e+fulfillEd25519 _ _ c = c+++readStandardFulfillment :: Fulfillment -> Either String Condition+readStandardFulfillment = readFulfillment
+ Network/CryptoConditions/Encoding.hs view
@@ -0,0 +1,89 @@+{-# LANGUAGE OverloadedStrings #-}++module Network.CryptoConditions.Encoding+  ( x690SortAsn+  , b64EncodeStripped+  , b64DecodeStripped+  , asnSeq+  , bytesOfUInt+  , uIntFromBytes+  , fiveBellsContainer+  , toData+  , toKey+  , parseASN1+  ) where+++import Crypto.Error (CryptoFailable(..))++import Data.ASN1.BinaryEncoding+import Data.ASN1.BinaryEncoding.Raw+import Data.ASN1.Encoding+import Data.ASN1.Parse+import Data.ASN1.Types+import Data.Bits+import qualified Data.ByteArray as BA+import qualified Data.ByteString as BS+import qualified Data.ByteString.Lazy as BL+import Data.ByteString.Base64.URL as B64+import qualified Data.ByteString.Char8 as C8+import Data.List (sortOn)+import Data.Monoid+import Data.Word+++b64EncodeStripped :: BS.ByteString -> BS.ByteString+b64EncodeStripped bs =+  let b64 = B64.encode bs+   in case C8.elemIndex '=' b64 of Just i -> BS.take i b64+                                   Nothing -> b64+++b64DecodeStripped :: BS.ByteString -> Either String BS.ByteString+b64DecodeStripped bs = +  let r = 4 - mod (BS.length bs) 4+      n = if r == 4 then 0 else r+   in B64.decode $ bs <> C8.replicate n '='+++x690SortAsn :: [[ASN1]] -> [[ASN1]]+x690SortAsn = sortOn (\a -> let b = encodeASN1' DER a in (BS.length b, b))+++asnSeq :: ASN1ConstructionType -> [ASN1] -> [ASN1]+asnSeq c args = [Start c] ++ args ++ [End c]+++fiveBellsContainer :: Integral i => i -> [BS.ByteString] -> [ASN1]+fiveBellsContainer tid bs =+  let c = Container Context $ fromIntegral tid+   in asnSeq c [Other Context i s | (i,s) <- zip [0..] bs]+++bytesOfUInt :: Integer -> [Word8]+bytesOfUInt = reverse . list+  where list i | i <= 0xff = [fromIntegral i]+               | otherwise = (fromIntegral i .&. 0xff)+                             : list (i `shiftR` 8)+++uIntFromBytes :: [Word8] -> Integer+uIntFromBytes ws =+  let ns = zip (fromIntegral <$> reverse ws) [0..]+   in foldl (\r (n,o) -> r .|. (n `shiftL` (o*8))) 0 ns+++parseASN1 :: BS.ByteString -> ParseASN1 a -> Either String a+parseASN1 bs act = showErr decoded >>= runParseASN1 act+  where decoded = decodeASN1 DER $ BL.fromStrict bs+        showErr = either (Left . show) Right+++toKey :: CryptoFailable a -> Either String a+toKey r = case r of+   CryptoPassed a -> return a+   CryptoFailed e -> Left $ show e+++toData :: BA.ByteArrayAccess a => a -> BS.ByteString+toData = BS.pack . BA.unpack
+ Network/CryptoConditions/Impl.hs view
@@ -0,0 +1,387 @@+{-# LANGUAGE OverloadedStrings #-}+++module Network.CryptoConditions.Impl where+++import Crypto.Hash+import qualified Crypto.PubKey.Ed25519 as Ed2++import Control.Monad (when)++import Data.ASN1.BinaryEncoding+import Data.ASN1.BinaryEncoding.Raw+import Data.ASN1.Encoding+import Data.ASN1.Parse+import Data.ASN1.Types+import Data.Bits+import qualified Data.ByteArray as BA+import qualified Data.ByteString as BS+import qualified Data.ByteString.Base64.URL as B64+import Data.List (sortOn)+import Data.Maybe+import Data.Monoid+import qualified Data.Set as Set+import qualified Data.Text as T+import Data.Text.Encoding (decodeUtf8)+import Data.Word++import Network.CryptoConditions.Encoding+++--------------------------------------------------------------------------------+-- | Class of things that are conditions+--+class Show c => IsCondition c where+  getCost :: c -> Int+  getType :: c -> ConditionType+  getFingerprint :: c -> BS.ByteString+  getFulfillment :: c -> Maybe BS.ByteString+  getSubtypes :: c -> Set.Set ConditionType+  parseFulfillment :: Int -> ParseASN1 c+  verifyMessage :: c -> Message -> Bool+  anon :: Int -> BS.ByteString -> Int -> Set.Set Int -> c+++-- Parameter aliases+type Message = BS.ByteString+type Fulfillment = BS.ByteString+type Preimage = BS.ByteString+type Prefix = BS.ByteString+type Fingerprint = BS.ByteString+++encodeCondition :: IsCondition c => c -> BS.ByteString+encodeCondition = encodeASN1' DER . encodeConditionASN+++encodeConditionASN :: IsCondition c => c -> [ASN1]+encodeConditionASN c =+  let ct = getType c+      fingerprint = getFingerprint c+      costBs = BS.pack $ bytesOfUInt $ fromIntegral $ getCost c+      subtypes = "\a" <> (typeMask $ Set.map typeId $ getSubtypes c)+      body = [fingerprint, costBs] +++             if hasSubtypes ct then [subtypes] else []+   in fiveBellsContainer (typeId ct) body+++getConditionURI :: IsCondition c => c -> T.Text+getConditionURI c =+  let ct = getType c+      f = decodeUtf8 $ b64EncodeStripped $ getFingerprint c+      cost = T.pack $ show $ getCost c+      subtypes = if hasSubtypes ct+                    then "&subtypes=" <> typeNames (getSubtypes c)+                    else ""+   in "ni:///" <> hashFunc ct <> ";" <> f+       <> "?fpt=" <> typeName ct <> "&cost="+       <> cost <> subtypes+++getFulfillmentBase64 :: IsCondition c => c -> Maybe T.Text+getFulfillmentBase64 =+  fmap (decodeUtf8 . B64.encode) . getFulfillment+++readFulfillment :: IsCondition c => Fulfillment -> Either String c+readFulfillment bs = parseASN1 bs parsePoly+++readFulfillmentBase64 :: IsCondition c => Fulfillment -> Either String c+readFulfillmentBase64 = readFulfillment . B64.decodeLenient +++parsePoly :: IsCondition c => ParseASN1 c+parsePoly = withContainerContext parseFulfillment+++validate :: IsCondition c => T.Text -> c -> Message -> Bool+validate condUri ffill msg =+  verifyMessage ffill msg && getConditionURI ffill == condUri +++--------------------------------------------------------------------------------+-- | Type of a condition+--+data ConditionType = CT+  { typeId :: Int+  , typeName :: T.Text+  , hasSubtypes :: Bool+  , hashFunc :: T.Text+  }+  deriving (Show)+++-- Eq and Ord instances consider only the ID+--+instance Eq ConditionType where+  ct == ct' = typeId ct == typeId ct'+++instance Ord ConditionType where+  ct <= ct' = typeId ct <= typeId ct'+++-- Functions for working with sets of condition types+--+typeNames :: Set.Set ConditionType -> T.Text+typeNames = T.intercalate "," . map typeName . Set.toAscList+++-- This should figure prepend the range itself.+typeMask :: Set.Set Int -> BS.ByteString+typeMask cts =+  let op i = shiftL 1 (7 - mod i 8)+      bits = Set.map op cts+      mask = foldl (.|.) 0 bits :: Int+   in BS.singleton $ fromIntegral mask+++unTypeMask :: BS.ByteString -> Set.Set Int+unTypeMask maskbs = Set.fromList $+  let [n, w] = fromIntegral <$> BS.unpack maskbs+   in filter (\i -> 0 /= w .&. (shiftL 1 (n-i))) [0..n]+++--------------------------------------------------------------------------------+-- | (0) Preimage Condition+--++preimageType :: ConditionType+preimageType = CT 0 "preimage-sha-256" False "sha-256"+++preimageFulfillment :: BS.ByteString -> Fulfillment+preimageFulfillment pre = encodeASN1' DER body+  where body = fiveBellsContainer (typeId preimageType) [pre]+++preimageCost :: BS.ByteString -> Int+preimageCost = BS.length+++preimageFingerprint :: Preimage -> Fingerprint+preimageFingerprint = sha256+++parsePreimage :: (Preimage -> c) -> ParseASN1 c+parsePreimage construct = construct <$> parseOther 0+++-- | The preimage is assumed to be correct if it has been provided;+--   it'll show up during URI comparison if it's wrong.+verifyPreimage :: Preimage -> Message -> Bool+verifyPreimage _ _ = True+++--------------------------------------------------------------------------------+-- | (1) Prefix condition+++prefixType :: ConditionType+prefixType = CT 1 "prefix-sha-256" True "sha-256"+++prefixCost :: IsCondition c => Prefix -> Int -> c -> Int+prefixCost pre maxMessageLength c =+  BS.length pre + getCost c + 1024 + maxMessageLength+++prefixFingerprint :: IsCondition c => Prefix -> Int -> c -> Fingerprint+prefixFingerprint pre mml cond = sha256 body+  where body = encodeASN1' DER asn+        mmlbs = BS.pack $ bytesOfUInt $ fromIntegral mml+        condAsn = encodeConditionASN cond+        asn = asnSeq Sequence $+              [ Other Context 0 pre+              , Other Context 1 mmlbs+              ] ++ asnSeq (Container Context 2) condAsn+++prefixFulfillment :: IsCondition c => Prefix -> Int -> c -> Maybe Fulfillment+prefixFulfillment pre mml cond =+  let mmlbs = BS.pack $ bytesOfUInt $ fromIntegral mml+      msubffill = getFulfillment cond+      getAsn subffill =+        let subAsn = either (error . show) id $ decodeASN1' DER $ subffill+         in asnSeq (Container Context 1) $+             [ Other Context 0 pre+             , Other Context 1 mmlbs+             ] ++ asnSeq (Container Context 2) subAsn+   in encodeASN1' DER . getAsn <$> msubffill+++prefixSubtypes :: IsCondition c => c -> Set.Set ConditionType+prefixSubtypes cond =+  let cts = Set.singleton $ getType cond+      all' = Set.union cts $ getSubtypes cond+   in Set.delete prefixType all'+++parsePrefix :: IsCondition c => (Prefix -> Int -> c -> c) -> ParseASN1 c+parsePrefix construct = do+  (pre, mmlbs) <- (,) <$> parseOther 0 <*> parseOther 1+  let mml = fromIntegral $ uIntFromBytes $ BS.unpack mmlbs+  cond <- onNextContainer (Container Context 2) parsePoly+  pure $ construct pre mml cond+++verifyPrefix :: IsCondition c => Prefix -> Int -> c -> Message -> Bool+verifyPrefix prefix mml cond msg =+  let ok = mml >= BS.length msg+   in verifyMessage cond (prefix <> msg)+++--------------------------------------------------------------------------------+-- | (2) Threshold condition+--++thresholdType :: ConditionType+thresholdType = CT 2 "threshold-sha-256" True "sha-256"+++thresholdFulfillment :: IsCondition c => Word16 -> [c] -> Maybe Fulfillment+thresholdFulfillment t subs =+  let ti = fromIntegral t+      withFf = zip subs (getFulfillment <$> subs)+      byCost = sortOn ffillCost withFf+      ffills = take ti $ catMaybes $ snd <$> byCost+      conds = encodeConditionASN . fst <$> drop ti byCost+      ffills' = either (error . show) id . decodeASN1' DER <$> ffills+      asn = asnSeq (Container Context 2) $+              asnSeq (Container Context 0) (concat ffills') +++              asnSeq (Container Context 1) (concat conds)+      encoded = encodeASN1' DER asn+   in if length ffills == ti then Just encoded else Nothing+  where+    -- order by has ffill then cost of ffill+    ffillCost (c, Just _) = (0::Int, getCost c)+    ffillCost _           = (1, 0)+++thresholdFingerprint :: IsCondition c => Word16 -> [c] -> Fingerprint+thresholdFingerprint t subs =+  let asns = encodeConditionASN <$> subs+   in thresholdFingerprintFromAsns t asns+++thresholdFingerprintFromAsns :: Word16 -> [[ASN1]] -> Fingerprint+thresholdFingerprintFromAsns t asns = +  let subs' = x690SortAsn asns -- TODO: encode only once?+      c = Container Context 1+      asn = asnSeq Sequence $+        [ Other Context 0 (BS.pack $ bytesOfUInt $ fromIntegral t)+        , Start c+        ] ++ concat subs' ++ [End c]+   in sha256 $ encodeASN1' DER asn+++thresholdSubtypes :: IsCondition c => [c] -> Set.Set ConditionType+thresholdSubtypes subs =+  let cts = Set.fromList (getType <$> subs)+      all' = Set.unions (cts : (getSubtypes <$> subs))+   in Set.delete thresholdType all'+++thresholdCost :: IsCondition c => Word16 -> [c] -> Int+thresholdCost t subs =+  let largest = take (fromIntegral t) $ sortOn (*(-1)) $ getCost <$> subs+   in sum largest + 1024 * length subs+++parseThreshold :: IsCondition c => (Word16 -> [c] -> c) -> ParseASN1 c+parseThreshold construct = do+  ffills <- onNextContainer (Container Context 0) $ getMany parsePoly+  conds <- onNextContainer (Container Context 1) $ getMany parseCondition+  let t = fromIntegral $ length ffills+  pure $ construct t (conds ++ ffills)+++parseCondition :: IsCondition c => ParseASN1 c+parseCondition = withContainerContext $ \tid -> do+  (bs, costbs) <- (,) <$> parseOther 0 <*> parseOther 1+  let cost = fromIntegral $ uIntFromBytes $ BS.unpack costbs+      condPart = anon tid bs cost+  subtypes <- if hasSubtypes $ getType $ condPart mempty+                 then unTypeMask <$> parseOther 2 else pure mempty+  pure $ condPart subtypes+++verifyThreshold :: IsCondition c => Word16 -> [c] -> Message -> Bool+verifyThreshold m subs msg =+  let m' = fromIntegral m+      doVerify c = verifyMessage c msg+   in m' == length (take m' $ filter (==True) $ map doVerify subs)+++--------------------------------------------------------------------------------+-- | (3) RSA-SHA256 Condition+--+++--------------------------------------------------------------------------------+-- | (4) ED25519-SHA256 Condition+--+++ed25519Type :: ConditionType+ed25519Type = CT 4 "ed25519-sha-256" False "sha-256"+++ed25519Cost :: Int+ed25519Cost = 131072+++ed25519Fingerprint :: Ed2.PublicKey -> Fingerprint+ed25519Fingerprint pk = sha256 body+  where body = encodeASN1' DER asn+        asn = [ Start Sequence+              , Other Context 0 $ toData pk+              ]+++ed25519Fulfillment :: Ed2.PublicKey -> Ed2.Signature -> Fulfillment+ed25519Fulfillment pk sig = encodeASN1' DER body+  where body = fiveBellsContainer (typeId ed25519Type) [toData pk, toData sig]+++parseEd25519 :: (Ed2.PublicKey -> Ed2.Signature -> c) -> ParseASN1 c+parseEd25519 construct = do+  (bspk, bssig) <- (,) <$> parseOther 0 <*> parseOther 1+  either throwParseError pure $+    construct <$> toKey (Ed2.publicKey bspk)+              <*> toKey (Ed2.signature bssig)+++verifyEd25519 :: Ed2.PublicKey -> Ed2.Signature -> Message -> Bool+verifyEd25519 pk = flip (Ed2.verify pk)++--------------------------------------------------------------------------------+-- Utilities+++sha256 :: BA.ByteArrayAccess a => a -> BS.ByteString+sha256 a = BS.pack $ BA.unpack $ (hash a :: Digest SHA256)+++withContainerContext :: (Int -> ParseASN1 a) -> ParseASN1 a+withContainerContext fp = do+  asn <- getNext+  case asn of+    (Start c@(Container Context tid)) -> do+      res <- fp tid+      end <- getNext+      if end /= End c then throwParseError "Failed parsing end"+                      else pure res+    other -> throwParseError ("Not a container context: " ++ show other)+++parseOther :: Int -> ParseASN1 BS.ByteString+parseOther n = do+  asn <- getNext+  case asn of+    (Other Context i bs) ->+      if n == i then pure bs+                else throwParseError $ "Invalid context id: " ++ show (n,i)+    other -> throwParseError "agh" -- TODO
+ README.md view
@@ -0,0 +1,31 @@+# Crypto Conditions++Targeting spec: draft-thomas-crypto-conditions-02 of December 20, 2016++## Current status++Supports all standard condition types except RSA.++Needs more testing. [Some tests](./test/) exist.++## Design++This approach to Crypto Conditions in Haskell has the goals:++* Simple to understand and work with+* Easily extensible++The bottleneck to achieving these goals is extensibility; Haskell does not+support dynamic dispatch like OOP languages, and runtime type casting+in Haskell is very unnatural and somewhat unsafe.++The solution is to decouple the condition type from the implementation,+such that the core algorithms and serialization can work with instances+of an "IsCondition" class, and a polymorphic data type can be implemented+separately to support the desired condition types and behaviours.++The module [Network.CryptoConditions](./Network/CryptoConditions.hs) supports the standard+condition types, library authors wishing to extend CryptoConditions+should copy and paste this file into their own project and define their own+Condition type.+
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ cryptoconditions.cabal view
@@ -0,0 +1,55 @@+name:                cryptoconditions+version:             0.1.0.0+synopsis:            Interledger Crypto-Conditions+description:         Please see README.md+homepage:            https://github.com/libscott/cryptoconditions-hs+license:             BSD3+license-file:        LICENSE+author:              Scott Sadler+maintainer:          Scott Sadler <scott@scottsadler.de>+category:            Crypto, Finance, Network+build-type:          Simple+extra-source-files:  README.md+copyright:           Copyright (C) 2017 Scott Sadler+cabal-version:       >=1.10++library+  hs-source-dirs:      .+  exposed-modules:     Network.CryptoConditions+                     , Network.CryptoConditions.Encoding+                     , Network.CryptoConditions.Impl+  build-depends:       base >= 4.7 && < 5+                     , asn1-encoding+                     , asn1-parse+                     , asn1-types+                     , base64-bytestring+                     , bytestring+                     , containers+                     , cryptonite+                     , memory+                     , text+  default-language:    Haskell2010++test-suite cryptoconditions-test+  type:                exitcode-stdio-1.0+  hs-source-dirs:      test+  main-is:             Tests.hs+  build-depends:       base+                     , cryptoconditions+                     , aeson+                     , asn1-encoding+                     , base16-bytestring+                     , base64-bytestring+                     , bytestring+                     , cryptonite+                     , aeson-quick+                     , text+                     , transformers+                     , tasty+                     , tasty-hunit+  ghc-options:         -threaded -rtsopts -with-rtsopts=-N+  default-language:    Haskell2010++source-repository head+  type:     git+  location: https://github.com/libscott/cryptoconditions-hs
+ test/Tests.hs view
@@ -0,0 +1,15 @@+{-# LANGUAGE OverloadedStrings #-}++import Test.Tasty+import Test.Tasty.HUnit++import TestFiveBells+import TestStandard+++main :: IO ()+main = defaultMain $ testGroup "Tests" [ fiveBellsSuite+                                       , standardTests+                                       ]++