packages feed

haskoin-0.1.0: tests/Network/Haskoin/Transaction/Tests.hs

module Network.Haskoin.Transaction.Tests (tests) where

import Test.Framework (Test, testGroup)
import Test.Framework.Providers.QuickCheck2 (testProperty)

import Data.Word (Word64)
import qualified Data.ByteString as BS (length)

import Network.Haskoin.Transaction.Arbitrary
import Network.Haskoin.Transaction.Builder
import Network.Haskoin.Protocol
import Network.Haskoin.Script
import Network.Haskoin.Crypto
import Network.Haskoin.Util

tests :: [Test]
tests = 
    [ testGroup "Building Transactions"
        [ testProperty "building address tx" testBuildAddrTx
        , testProperty "testing guessTxSize function" testGuessSize
        , testProperty "testing chooseCoins function" testChooseCoins
        , testProperty "testing chooseMSCoins function" testChooseMSCoins
        ]
    -- TODO: Turn these tests into unit tests as they are very heavy 
    , testGroup "Signing Transactions"
        [ testProperty "Sign and validate PKHash transactions" testSignTx
        , testProperty "Sign and validate Multisig transactions" testSignMS
        ]
    ]

{- Building Transactions -}

testBuildAddrTx :: [OutPoint] -> Address -> Word64 -> Bool
testBuildAddrTx os a v 
    | v <= 2100000000000000 = isRight tx && case a of
        x@(PubKeyAddress _) -> Right (PayPKHash x) == out
        x@(ScriptAddress _) -> Right (PayScriptHash x) == out
    | otherwise = isLeft tx
    where tx  = buildAddrTx os [(addrToBase58 a,v)]
          out = decodeOutputBS $ scriptOutput $ txOut (fromRight tx) !! 0

testGuessSize :: SpendAddrTx -> Bool
testGuessSize (SpendAddrTx tx) =
    -- We compute an upper bound but it should be close enough to the real size
    -- We give 2 bytes of slack on every signature (1 on r and 1 on s)
    guess >= len && guess - 2*delta <= len
    where delta   = pki + (sum $ map fst msi)
          guess   = guessTxSize pki msi pkout msout
          len     = BS.length $ encode' tx
          ins     = map f $ txIn tx
          f i     = fromRight $ decodeInputBS $ scriptInput i
          pki     = length $ filter isSpendPKHash ins
          msi     = concat $ map shData ins
          shData (ScriptHashInput _ (PayMulSig keys r)) = [(r,length keys)]
          shData _ = []
          out      = map (fromRight . decodeOutputBS . scriptOutput) $ txOut tx
          pkout    = length $ filter isPayPKHash out
          msout    = length $ filter isPayScriptHash out

testChooseCoins :: Word64 -> Word64 -> [Coin] -> Bool
testChooseCoins target kbfee xs = case chooseCoins target kbfee xs of
    Right (chosen,change) ->
        let outSum = sum $ map coinValue chosen
            fee    = getFee kbfee (length chosen) 
        in outSum == target + change + fee
    Left _ -> 
        let fee = getFee kbfee (length xs) 
        in target == 0 || s < target || s < target + fee
    where s = sum $ map coinValue xs

testChooseMSCoins :: Word64 -> Word64 -> MSParam -> [Coin] -> Bool
testChooseMSCoins target kbfee (MSParam m n) xs = 
    case chooseMSCoins target kbfee (m,n) xs of
        Right (chosen,change) ->
            let outSum = sum $ map coinValue chosen
                fee    = getMSFee kbfee (m,n) (length chosen) 
            in outSum == target + change + fee
        Left _ -> 
            let fee = getMSFee kbfee (m,n) (length xs) 
            in target == 0 || s < target + fee
    where s = sum $ map coinValue xs

{- Signing Transactions -}

testSignTx :: PKHashSigTemplate -> Bool
testSignTx (PKHashSigTemplate tx sigi prv)
    | null $ txIn tx = (isLeft res) && (isLeft resP)
    | otherwise = (not $ verifyStdTx tx verData)
                      && isRight res && stat
                      && verifyStdTx txSig verData
                      && isRight resP && (not statP)
                      && (not $ verifyStdTx txSigP verData)
                      && isRight resC && statC
                      && verifyStdTx txSigC verData
    where res             = detSignTx tx sigi prv
          (txSig, stat)   = fromRight res
          resP            = detSignTx tx sigi (tail prv)
          (txSigP, statP) = fromRight resP
          resC            = detSignTx txSigP sigi [head prv]
          (txSigC, statC) = fromRight resC
          verData = map (\(SigInput s o _ _) -> (s,o)) sigi
         
testSignMS :: MulSigTemplate -> Bool
testSignMS (MulSigTemplate tx sigi prv)
    | null $ txIn tx = (isLeft res) && (isLeft resP)
    | otherwise = (not $ verifyStdTx tx verData)
                      && isRight res && stat
                      && verifyStdTx txSig verData
                      && isRight resP && (not statP)
                      && (not $ verifyStdTx txSigP verData)
                      && isRight resC && statC
                      && verifyStdTx txSigC verData
    where res             = detSignTx tx sigi prv
          (txSig, stat)   = fromRight res
          resP            = detSignTx tx sigi (tail prv)
          (txSigP, statP) = fromRight resP
          resC            = detSignTx txSigP sigi [head prv]
          (txSigC, statC) = fromRight resC
          verData = map (\(SigInput s o _ _) -> (s,o)) sigi