packages feed

tahoe-chk-0.2.0.0: src/Tahoe/CHK/Validate.hs

{-# LANGUAGE ScopedTypeVariables #-}

module Tahoe.CHK.Validate where

import Control.Lens (view)
import Crypto.Hash (HashAlgorithm)
import Data.Bifunctor (Bifunctor (first))
import qualified Data.ByteString.Lazy as LB
import Tahoe.CHK.Capability (Verifier, fingerprint)
import Tahoe.CHK.Crypto (blockHash', ciphertextSegmentHash', uriExtensionHash)
import Tahoe.CHK.Merkle (checkMerkleProof, heightForLeafCount, leafHashes, rootHash)
import Tahoe.CHK.SHA256d (Digest', SHA256d)
import Tahoe.CHK.Share (Crypttext, Share (..), blockHashTree, blocks, crypttextHashTree, neededHashes, uriExtension)
import Tahoe.CHK.URIExtension (crypttextRootHash, shareRootHash, totalShares)

{- | Determine the validity of the given share's fingerprint as defined by the
 given capability.
-}
validFingerprint :: Verifier -> Share -> Bool
validFingerprint cap = (== view fingerprint cap) . uriExtensionHash . _uriExtension

{- | True if the root of the crypttext hash tree in the share matches the
 crypttext hash root given in the URI extension block.  False otherwise.
-}
matchingCrypttextHashRoot :: Share -> Bool
matchingCrypttextHashRoot share = inShare == inUEB
  where
    inShare = rootHash . view crypttextHashTree $ share
    inUEB = view (uriExtension . crypttextRootHash) share

{- | True if the share's own hash in the `shareNeededHashes` list equals the
 root of the share's block hash merkle tree, False otherwise.
-}
matchingBlockHashRoot :: Int -> Share -> Bool
matchingBlockHashRoot shareNum share =
    -- We should find exactly one element with a share number matching our
    -- share number and the associated hash should match our hash.  If we find
    -- none or more than one then the share is mis-encoded and we should fail
    -- validation (though maybe we should do so with a distinct error value).
    isMatching
  where
    isMatching =
        checkMatch
            . findOwnHash
            $ view neededHashes share

    checkMatch = ([rootHash (view blockHashTree share)] ==) . map snd

    -- Note that shareNeededHashes contains "node numbers" while our
    -- shareNum is a "leaf number".  So, convert.
    findOwnHash = filter ((== nodeNumber) . fst)

    nodeNumber :: Int
    nodeNumber = toNodeNumber shareNum

    toNodeNumber num = num + (2 ^ treeHeight) - 1
    treeHeight = heightForLeafCount . view (uriExtension . totalShares) $ share

{- | Determine the validity of each of the given shares' "share root hash"
 values with respect to the other shares in the list.
-}
validShareRootHash :: [(Int, Share)] -> [Bool]
validShareRootHash [] = []
validShareRootHash shares@((_, aShare) : _) =
    isValid
  where
    isValid = zipWith (`checkMerkleProof` expected) proofs leafs

    -- You already validated the share fingerprint so the expected share root
    -- hash from the UEB has also been validated and we can use it.  The UEB
    -- is the same for all shares so we can pull this value from an arbitrary
    -- share.
    expected = view (uriExtension . shareRootHash) aShare

    -- Extract the proof for each share in the given list.
    proofs = uncurry oneProof <$> shares

    -- Also extract each share's leaf hash to supply to the proof checker.
    leafs = rootHash . view blockHashTree . snd <$> shares

    oneProof :: Int -> Share -> [(Int, Digest' SHA256d)]
    oneProof shareNum share = fmap (first (+ 1)) proof
      where
        -- The length of the proof equals the height of the tree.
        treeHeight = length (view neededHashes share)

        -- Since inclusion of our block tree root hash is what the proof is
        -- proving we don't want it.  We need to take it out to use our proof
        -- checker.  That means we need to find it.  The "needed hashes" are
        -- labeled by tree _node number_ and our share number is effectively a
        -- _leaf number_ so we need to convert for comparison.

        -- Nodes are numbered consecutively, starting at 0 for the root node
        -- and proceeding left-to-right depth-first.
        firstLeafNum = 2 ^ (treeHeight - 1) - 1
        nodeNum = firstLeafNum + shareNum

        -- The proof is all of the needed hashes except for this share's own
        -- hash which we will feed into the proof checker separately.
        proof = filter ((/= nodeNum) . fst) (first fromIntegral <$> view neededHashes share)

showHashes :: (Show a, Show b) => [(a, b)] -> String
showHashes = unwords . fmap showHash

showHash :: (Show a, Show b) => (a, b) -> String
showHash (n, bs) = unwords [show n, show bs]

{- | Get only and all the blocks from the given share with hashes that match
 the values in the Share's "block hash tree".
-}
shareValidBlocks :: Share -> [Maybe LB.ByteString]
shareValidBlocks share =
    zipWith checkHash (view blocks share) (leafHashes (view blockHashTree share))
  where
    checkHash :: forall hash. HashAlgorithm hash => LB.ByteString -> Digest' hash -> Maybe LB.ByteString
    checkHash bs expected
        | blockHash' (LB.toStrict bs) == expected = Just bs
        | otherwise = Nothing

{- | Compare the hash of one segment to an expected hash value and return
 Nothing if it does not match or Just the segment if it does.
-}
validSegment :: Digest' SHA256d -> Crypttext -> Maybe Crypttext
validSegment expected crypttext
    | ciphertextSegmentHash' crypttext == expected = Just crypttext
    | otherwise = Nothing

-- | Apply @validSegment@ to lists of values.
validSegments :: [Digest' SHA256d] -> [Crypttext] -> [Maybe Crypttext]
validSegments = zipWith validSegment