packages feed

sync-mht-0.2.1.0: src/main/hs/Sync/MerkleTree/Trie.hs

{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveDataTypeable #-}
module Sync.MerkleTree.Trie where

import Prelude hiding (lookup)
import qualified Data.List as L
import Data.Typeable
import Data.Byteable
import Crypto.Hash
import qualified Data.Set as S
import Data.Set(Set)
import Data.Array.IArray
import Control.Arrow hiding (arr, loop)
import qualified Data.Text.Encoding as TE
import qualified Data.Text as T
import GHC.Generics
import qualified Data.ByteString.Base16 as B16
import qualified Data.ByteString as BS
import qualified Data.Serialize as SE

data Hash = Hash { unHash :: BS.ByteString }
    deriving (Eq, Ord, Generic)

instance Show Hash where
    showsPrec i x = showsPrec i (go x)
        where
          go :: Hash -> String
          go (Hash dig) = T.unpack $ TE.decodeUtf8 $ B16.encode dig

instance Read Hash where
    readsPrec i = map (first go) . readsPrec i
        where
          go :: String -> Hash
          go s = Hash . fst . B16.decode $ TE.encodeUtf8 $ T.pack s

instance SE.Serialize Hash

data Trie a
    = Trie
      { t_hash :: !Hash
      , t_node :: !(TrieNode a)
      }
      deriving (Eq, Read, Show)

data TrieNode a
    = Node !(Array Int (Trie a))
    | Leave !(Set a)
    deriving (Eq, Read, Show)

data NodeType = NodeType | LeaveType
    deriving (Eq, Read, Show, Generic)
instance SE.Serialize NodeType

data TrieLocation
    = TrieLocation
    { tl_level :: Int
    , tl_index :: Int
    }
    deriving (Read, Show, Generic)

instance SE.Serialize TrieLocation

degree :: Int
degree = 64

class HasDigest a where
    digest :: a -> Digest SHA256

data Fingerprint
    = Fingerprint
      { f_hash :: Hash
      , f_nodeType :: NodeType
      }
      deriving (Eq, Read, Show, Generic, Typeable)

instance SE.Serialize Fingerprint

toFingerprint :: Trie a -> Fingerprint
toFingerprint (Trie h node) = Fingerprint h nodeType
     where
       nodeType =
           case node of
             Node _ -> NodeType
             Leave _ -> LeaveType

mkTrie :: (Ord a, HasDigest a) => Int -> [a] -> Trie a
mkTrie i ls
    | length ls < degree = mkLeave ls
    | otherwise =
        mkNode $ fmap (mkTrie (i+1)) $ accumArray (flip (:)) [] (0,degree-1) $ map ((groupOf i) &&& id) ls

mkNode :: (Array Int (Trie a)) -> Trie a
mkNode arr =
    Trie
    { t_hash = combineHash $ map t_hash $ elems arr
    , t_node = Node arr
    }

hashSHA256 :: BS.ByteString -> Digest SHA256
hashSHA256 = hash

combineHash :: [Hash] -> Hash
combineHash = Hash . toBytes . hashSHA256 . BS.concat . map unHash

groupOf :: (HasDigest a) => Int -> a -> Int
groupOf i x = fromInteger $ toInteger $ (h0 `mod` (fromInteger $ toInteger degree))
     where
       Just (h0, _t) = BS.uncons $ toBytes $ h
       h :: Digest SHA256
       h = hash $ BS.concat [BS.pack [fromInteger $ toInteger i], toBytes $ digest x]

mkLeave :: (HasDigest a, Ord a) => [a] -> Trie a
mkLeave ls =
    Trie
    { t_hash = combineHash $ map (Hash . toBytes . digest) $ L.sort ls
    , t_node = Leave $ S.fromList ls
    }

lookup :: Trie a -> TrieLocation -> Trie a
lookup trie (TrieLocation { tl_level = l, tl_index = i })
    | l < 0 || i < 0 || i >= degree^l = error "illegal index pair"
    | l > 0, (g, i') <- i `quotRem` (degree ^ (l-1)), Node arr <- t_node trie =
        lookup (arr ! g) (TrieLocation { tl_level = (l - 1), tl_index =  i'})
    | l == 0 = trie
    | otherwise = error "index pair to deep"

queryHash :: Trie a -> TrieLocation -> Fingerprint
queryHash trie = toFingerprint . lookup trie

querySet :: (Ord a) => Trie a -> TrieLocation -> Set a
querySet trie = getAll . lookup trie

getAll :: (Ord a) => Trie a -> Set a
getAll (Trie _ node) =
    case node of
      Node arr -> S.unions $ map getAll $ elems arr
      Leave s -> s

rootLocation :: TrieLocation
rootLocation =
    TrieLocation
    { tl_level = 0
    , tl_index = 0
    }

expand :: TrieLocation -> (Array Int (Trie a)) -> [(TrieLocation, Trie a)]
expand loc arr = map go [0..(degree - 1)]
    where
      go i =
          ( TrieLocation
            { tl_level = tl_level loc + 1
            , tl_index = degree * tl_index loc + i }
          , arr ! i
          )