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 +30/−0
- Network/CryptoConditions.hs +107/−0
- Network/CryptoConditions/Encoding.hs +89/−0
- Network/CryptoConditions/Impl.hs +387/−0
- README.md +31/−0
- Setup.hs +2/−0
- cryptoconditions.cabal +55/−0
- test/Tests.hs +15/−0
+ 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+ ]++