haskoin-0.1.0: Network/Haskoin/Crypto/Merkle.hs
module Network.Haskoin.Crypto.Merkle
( calcTreeHeight
, calcTreeWidth
, buildMerkleRoot
, calcHash
, buildPartialMerkle
, extractMatches
) where
import Data.Bits
import Data.Maybe
import qualified Data.ByteString as BS
import Network.Haskoin.Crypto.Hash
import Network.Haskoin.Crypto.BigWord
import Network.Haskoin.Util
import Network.Haskoin.Constants
type MerkleRoot = Word256
type FlagBits = [Bool]
type PartialMerkleTree = [Word256]
-- | Computes the height of a merkle tree.
calcTreeHeight :: Int -- ^ Number of transactions (leaf nodes).
-> Int -- ^ Height of the merkle tree.
calcTreeHeight ntx = ceiling $ log (fromIntegral ntx :: Double) / log 2
-- | Computes the width of a merkle tree at a specific height. The transactions
-- are at height 0.
calcTreeWidth :: Int -- ^ Number of transactions (leaf nodes).
-> Int -- ^ Height at which we want to compute the width.
-> Int -- ^ Width of the merkle tree.
calcTreeWidth ntx h = (ntx + (1 `shiftL` h) - 1) `shiftR` h
-- | Computes the root of a merkle tree from a list of leaf node hashes.
buildMerkleRoot :: [TxHash] -- ^ List of transaction hashes (leaf nodes).
-> MerkleRoot -- ^ Root of the merkle tree.
buildMerkleRoot txs = calcHash (calcTreeHeight $ length txs) 0 txs
hash2 :: Word256 -> Word256 -> Word256
hash2 a b = doubleHash256 $ encode' a `BS.append` encode' b
-- | Computes the hash of a specific node in a merkle tree.
calcHash :: Int -- ^ Height of the node in the merkle tree.
-> Int -- ^ Position of the node (0 for the leftmost node).
-> [TxHash] -- ^ Transaction hashes of the merkle tree (leaf nodes).
-> Word256 -- ^ Hash of the node at the specified position.
calcHash height pos txs
| height < 0 || pos < 0 = error "calcHash: Invalid parameters"
| height == 0 = fromIntegral $ txs !! pos
| otherwise = hash2 left right
where
left = calcHash (height-1) (pos*2) txs
right | pos*2+1 < calcTreeWidth (length txs) (height-1) =
calcHash (height-1) (pos*2+1) txs
| otherwise = left
-- | Build a partial merkle tree.
buildPartialMerkle
:: [(TxHash,Bool)]
-- ^ List of transactions hashes forming the leaves of the merkle tree
-- and a bool indicating if that transaction should be included in the
-- partial merkle tree.
-> (FlagBits, PartialMerkleTree)
-- ^ Flag bits (used to parse the partial merkle tree) and the
-- partial merkle tree.
buildPartialMerkle hs = traverseAndBuild (calcTreeHeight $ length hs) 0 hs
traverseAndBuild :: Int -> Int -> [(TxHash,Bool)]
-> (FlagBits, PartialMerkleTree)
traverseAndBuild height pos txs
| height < 0 || pos < 0 = error "traverseAndBuild: Invalid parameters"
| height == 0 || not match = ([match],[calcHash height pos t])
| otherwise = (match : lb ++ rb, lh ++ rh)
where
t = map fst txs
s = pos `shiftL` height
e = min (length txs) $ (pos+1) `shiftL` height
match = or $ map snd $ take (e-s) $ drop s txs
(lb,lh) = traverseAndBuild (height-1) (pos*2) txs
(rb,rh) | (pos*2+1) < calcTreeWidth (length txs) (height-1)
= traverseAndBuild (height-1) (pos*2+1) txs
| otherwise = ([],[])
traverseAndExtract :: Int -> Int -> Int -> FlagBits -> PartialMerkleTree
-> Maybe (MerkleRoot, [TxHash], Int, Int)
traverseAndExtract height pos ntx flags hashes
| length flags == 0 = Nothing
| height == 0 || not match = leafResult
| isNothing leftM = Nothing
| (pos*2+1) >= calcTreeWidth ntx (height-1) =
Just (hash2 lh lh, lm, lcf+1, lch)
| isNothing rightM = Nothing
| otherwise =
Just (hash2 lh rh, lm ++ rm, lcf+rcf+1, lch+rch)
where
leafResult
| null hashes = Nothing
| otherwise = Just
(h,if height == 0 && match then [fromIntegral h] else [],1,1)
(match:fs) = flags
(h:_) = hashes
leftM = traverseAndExtract (height-1) (pos*2) ntx fs hashes
(lh,lm,lcf,lch) = fromJust leftM
rightM = traverseAndExtract (height-1) (pos*2+1) ntx
(drop lcf fs) (drop lch hashes)
(rh,rm,rcf,rch) = fromJust rightM
-- | Extracts the matching hashes from a partial merkle tree. This will return
-- the list of transaction hashes that have been included (set to True) in
-- a call to 'buildPartialMerkle'.
extractMatches :: FlagBits -- ^ Flag bits (produced by buildPartialMerkle).
-> PartialMerkleTree -- ^ Partial merkle tree.
-> Int -- ^ Number of transaction at height 0 (leaf nodes).
-> Either String (MerkleRoot, [TxHash])
-- ^ Merkle root and the list of matching transaction hashes.
extractMatches flags hashes ntx
| ntx == 0 = Left $
"extractMatches: number of transactions can not be 0"
| ntx > maxBlockSize `div` 60 = Left $
"extractMatches: number of transactions excessively high"
| length hashes > ntx = Left $
"extractMatches: More hashes provided than the number of transactions"
| length flags < length hashes = Left $
"extractMatches: At least one bit per node and one bit per hash"
| isNothing resM = Left $
"extractMatches: traverseAndExtract failed"
| (nBitsUsed+7) `div` 8 /= (length flags+7) `div` 8 = Left $
"extractMatches: All bits were not consumed"
| nHashUsed /= length hashes = Left $
"extractMatches: All hashes were not consumed: " ++ (show nHashUsed)
| otherwise = return (merkleRoot, matches)
where
resM = traverseAndExtract (calcTreeHeight ntx) 0 ntx flags hashes
(merkleRoot, matches, nBitsUsed, nHashUsed) = fromJust resM