packages feed

haskoin-core-0.13.2: test/Haskoin/Address/Bech32Spec.hs

{-# LANGUAGE OverloadedStrings #-}
module Haskoin.Address.Bech32Spec
    ( spec
    ) where

import           Control.Monad
import           Data.Bits               (xor)
import           Data.ByteString         (ByteString)
import qualified Data.ByteString         as B
import           Data.Char               (chr, ord, toLower)
import           Data.Maybe
import           Data.String.Conversions
import           Data.Text               (Text, append, pack, snoc, uncons)
import qualified Data.Text               as T
import           Data.Word               (Word8)
import           Haskoin.Address
import           Haskoin.Address.Bech32
import           Haskoin.Util
import           Test.Hspec
import           Test.HUnit

spec = do
    describe "bech32 checksum" $ do
        it "should have valid checksum" $ forM_ validChecksums testValidChecksum
        it "should have invalid checksum" $
            forM_ invalidChecksums testInvalidChecksum
        it "should be a valid address" $ forM_ validAddresses testValidAddress
        it "should be an invalid address" $
            forM_ invalidAddresses testInvalidAddress
        it "should be same for the input in different case" $
            all (== Just "test12hrzfj") . map (flip bech32Encode []) $ hrpCaseVariants
    describe "more encoding/decoding cases" $ do
        it "length > 90" $
            assert $
            isNothing $
            bech32Encode "bc" (replicate 82 (word5 (1 :: Word8)))
        it "segwit version bounds" $
            assert $ isNothing $ segwitEncode "bc" 17 []
        it "segwit prog len version 0" $
            assert $ isNothing $ segwitEncode "bc" 0 (replicate 30 1)
        it "segwit prog len version != 0" $
            assert $ isJust $ segwitEncode "bc" 1 (replicate 30 1)
        it "segwit prog len version != 0" $
            assert $ isNothing $ segwitEncode "bc" 1 (replicate 41 1)
        it "empty HRP encode" $ assert $ isNothing $ bech32Encode "" []
        it "empty HRP encode" $
            assert $ isNothing $ bech32Decode "10a06t8"
        it "hrp lowercased" $
            bech32Encode "HRP" [] `shouldBe` bech32Encode "hrp" []


testValidChecksum :: Bech32 -> Assertion
testValidChecksum checksum = case bech32Decode checksum of
    Nothing                      -> assertFailure (show checksum)
    Just (resultHRP, resultData) -> do
        -- test that a corrupted checksum fails decoding.
        let (hrp, rest)         = T.breakOnEnd "1" checksum
            Just (first, rest') = uncons rest
            checksumCorrupted   = (hrp `snoc` chr (ord first `xor` 1)) `append` rest'
        assertBool (show checksum ++ " corrupted")
            $ isNothing (bech32Decode checksumCorrupted)
        -- test that re-encoding the decoded checksum results in the same checksum.
        let checksumEncoded  = bech32Encode resultHRP resultData
            expectedChecksum = Just $ T.toLower checksum
        assertEqual (show checksum ++ " re-encode")
                    expectedChecksum
                    checksumEncoded

testInvalidChecksum :: Bech32 -> Assertion
testInvalidChecksum checksum =
    assertBool (show checksum) (isNothing $ bech32Decode checksum)

testValidAddress :: (Text, Text) -> Assertion
testValidAddress (address, hexscript) = do
    let address' = T.toLower address
        hrp      = T.take 2 address'
    case segwitDecode hrp address of
        Nothing                -> assertFailure "decode failed"
        Just (witver, witprog) -> do
            assertEqual (show address)
                        (decodeHex hexscript)
                        (Just $ segwitScriptPubkey witver witprog)
            assertEqual (show address)
                        (Just address')
                        (segwitEncode hrp witver witprog)

testInvalidAddress :: Text -> Assertion
testInvalidAddress address = do
    assertBool (show address) (isNothing $ segwitDecode "bc" address)
    assertBool (show address) (isNothing $ segwitDecode "tb" address)

segwitScriptPubkey :: Word8 -> [Word8] -> ByteString
segwitScriptPubkey witver witprog =
    B.pack $ witver' : fromIntegral (length witprog) : witprog
    where witver' = if witver == 0 then 0 else witver + 0x50

validChecksums :: [Text]
validChecksums =
    [ "A12UEL5L"
    , "an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1tt5tgs"
    , "abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw"
    , "11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j"
    , "split1checkupstagehandshakeupstreamerranterredcaperred2y9e3w"
    ]

invalidChecksums :: [Text]
invalidChecksums =
    [ " 1nwldj5"
    , "\DEL1axkwrx"
    , "an84characterslonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1569pvx"
    , "pzry9x0s0muk"
    , "1pzry9x0s0muk"
    , "x1b4n0q5v"
    , "li1dgmt3"
    , "de1lg7wt\xFF"
    ]

validAddresses :: [(Text, Text)]
validAddresses =
    [ ("BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4", "0014751e76e8199196d454941c45d1b3a323f1433bd6")
    , ("tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7"
      ,"00201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262")
    , ("bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx"
      ,"5128751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6")
    , ("BC1SW50QA3JX3S", "6002751e")
    , ("bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj", "5210751e76e8199196d454941c45d1b3a323")
    , ("tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy"
      ,"0020000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433")
    ]

invalidAddresses :: [Text]
invalidAddresses =
    [ "tc1qw508d6qejxtdg4y5r3zarvary0c5xw7kg3g4ty"
    , "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5"
    , "BC13W508D6QEJXTDG4Y5R3ZARVARY0C5XW7KN40WF2"
    , "bc1rw5uspcuh"
    , "bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90"
    , "BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P"
    , "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sL5k7"
    , "bc1zw508d6qejxtdg4y5r3zarvaryvqyzf3du"
    , "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3pjxtptv"
    , "bc1gmk9yu"
    ]

hrpCaseVariants:: [Text]
hrpCaseVariants = map T.pack hrpTestPermutations

hrpTestPermutations :: [String]
hrpTestPermutations = do
    a <- ['t', 'T']
    b <- ['e', 'E']
    c <- ['s', 'S']
    d <- ['t', 'T']
    return [a, b, c, d]