{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
module Main where
import Data.Aeson ((.:))
import qualified Data.Aeson as A
import qualified Data.ByteString as BS
import qualified Data.ByteString.Base16 as B16
import qualified Data.ByteString.Base58 as B58
import qualified Data.ByteString.Base58Check as B58Check
import qualified Data.Text.Encoding as TE
import qualified Data.Text.IO as TIO
import Test.Tasty
import Test.Tasty.HUnit
import qualified Test.Tasty.QuickCheck as Q
data Valid_Base58Check = Valid_Base58Check {
vc_string :: !BS.ByteString
, vc_payload :: !BS.ByteString
} deriving Show
decodeLenient :: BS.ByteString -> BS.ByteString
decodeLenient bs = case B16.decode bs of
Nothing -> error "boom"
Just v -> v
instance A.FromJSON Valid_Base58Check where
parseJSON = A.withObject "Valid_Base58Check" $ \m -> Valid_Base58Check
<$> fmap TE.encodeUtf8 (m .: "string")
<*> fmap (decodeLenient . TE.encodeUtf8) (m .: "payload")
data Invalid_Base58Check = Invalid_Base58Check {
ic_string :: !BS.ByteString
} deriving Show
instance A.FromJSON Invalid_Base58Check where
parseJSON = A.withObject "Invalid_Base58Check" $ \m -> Invalid_Base58Check
<$> fmap TE.encodeUtf8 (m .: "string")
data Base58Check = Base58Check {
b58c_valid :: ![Valid_Base58Check]
, b58c_invalid :: ![Invalid_Base58Check]
} deriving Show
instance A.FromJSON Base58Check where
parseJSON = A.withObject "Base58Check" $ \m -> Base58Check
<$> (m .: "valid")
<*> (m .: "invalid")
execute_base58check :: Base58Check -> TestTree
execute_base58check Base58Check {..} = testGroup "base58check" [
testGroup "valid" (fmap execute_valid b58c_valid)
, testGroup "invalid" (fmap execute_invalid b58c_invalid)
]
where
execute_valid Valid_Base58Check {..} = testCase "valid" $ do -- label
let enc = B58Check.encode vc_payload
assertEqual mempty enc vc_string
execute_invalid Invalid_Base58Check {..} = testCase "invalid" $ do -- label
let dec = B58Check.decode ic_string
is_just = \case
Nothing -> False
Just _ -> True
assertBool mempty (not (is_just dec))
data Valid_Base58 = Valid_Base58 {
vb_decodedHex :: !BS.ByteString
, vb_encoded :: !BS.ByteString
} deriving Show
instance A.FromJSON Valid_Base58 where
parseJSON = A.withObject "Valid_Base58" $ \m -> Valid_Base58
<$> fmap (decodeLenient . TE.encodeUtf8) (m .: "decodedHex")
<*> fmap TE.encodeUtf8 (m .: "encoded")
execute_base58 :: Valid_Base58 -> TestTree -- XX label
execute_base58 Valid_Base58 {..} = testCase "base58" $ do
let enc = B58.encode vb_decodedHex
assertEqual mempty enc vb_encoded
newtype BS = BS BS.ByteString
deriving (Eq, Show)
bytes :: Int -> Q.Gen BS.ByteString
bytes k = do
l <- Q.chooseInt (0, k)
v <- Q.vectorOf l Q.arbitrary
pure (BS.pack v)
instance Q.Arbitrary BS where
arbitrary = do
b <- bytes 1024
pure (BS b)
base58_decode_inverts_encode :: BS -> Bool
base58_decode_inverts_encode (BS bs) = case B58.decode (B58.encode bs) of
Nothing -> False
Just b -> b == bs
base58check_decode_inverts_encode :: BS -> Bool
base58check_decode_inverts_encode (BS bs) =
case B58Check.decode (B58Check.encode bs) of
Nothing -> False
Just b -> b == bs
main :: IO ()
main = do
scure_base58 <- TIO.readFile "etc/base58.json"
scure_base58check <- TIO.readFile "etc/base58_check.json"
let per = do
b0 <- A.decodeStrictText scure_base58 :: Maybe [Valid_Base58]
b1 <- A.decodeStrictText scure_base58check :: Maybe Base58Check
pure (b0, b1)
case per of
Nothing -> error "couldn't parse vectors"
Just (b58, b58c) -> defaultMain $ testGroup "ppad-base58" [
testGroup "unit tests" [
testGroup "base58" (fmap execute_base58 b58)
, execute_base58check b58c
]
, testGroup "property tests" [
Q.testProperty "(base58) decode . encode ~ id" $
Q.withMaxSuccess 250 base58_decode_inverts_encode
, Q.testProperty "(base58check) decode . encode ~ id" $
Q.withMaxSuccess 250 base58check_decode_inverts_encode
]
]