tahoe-chk-0.2.0.0: src/Tahoe/CHK/URIExtension.hs
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
module Tahoe.CHK.URIExtension (
URIExtension (..),
codecName,
codecParams,
tailCodecParams,
size,
segmentSize,
numSegments,
neededShares,
totalShares,
crypttextHash,
crypttextRootHash,
shareRootHash,
uriExtensionToBytes,
showBytes,
pURIExtension,
) where
import Control.Applicative.Combinators (count)
import Control.Applicative.Permutations (runPermutation, toPermutation)
import Control.Lens (view)
import Control.Lens.TH (makeLenses)
import Control.Monad (join, void, (>=>))
import Crypto.Hash (HashAlgorithm, digestFromByteString)
import Data.TreeDiff.Class (ToExpr)
import Data.Void (Void)
import GHC.Generics (Generic)
import Tahoe.CHK.SHA256d (Digest' (Digest'), SHA256d, toBytes)
import Text.Megaparsec (
MonadParsec (takeP),
Parsec,
anySingle,
)
import Text.Megaparsec.Byte (string)
import Text.Megaparsec.Byte.Lexer (decimal)
import Data.ByteString.Base32 (
encodeBase32Unpadded,
)
import qualified Data.ByteString as B
import qualified Data.Text as T
import Data.Text.Encoding (
decodeLatin1,
encodeUtf8,
)
import Data.List (
sort,
)
import qualified Tahoe.CHK.Parsing
import Tahoe.CHK.Types (
CrypttextHash,
Parameters (..),
Required,
SegmentNum,
Size,
Total,
)
import Tahoe.Netstring (
netstring,
)
-- | Represent additional metadata that appears at the end of each share.
data URIExtension = URIExtension
{ -- | The name of the encoding function. Only "zfec" is implemented.
_codecName :: B.ByteString
, -- | The parameters for the encoding function for all except the final
-- segment.
_codecParams :: Parameters
, -- | The parameters for the encoding function for the final segment.
_tailCodecParams :: Parameters
, -- | The application data size in bytes.
_size :: Size
, -- | The individual segment size in bytes.
_segmentSize :: Size
, -- | The number of segments of application data. Note the last segment
-- may be short so it is not necessarily the case that uriExtSize ==
-- uriExtSegmentSize * uriExtNumSegments.
_numSegments :: SegmentNum
, -- | The required (K) parameter to the encoding function. This is a
-- duplicate of the values in uriExtCodecParams and uriExtTailCodecParams.
_neededShares :: Required
, -- | The total (N) parameter to the encoding function. This too is a
-- duplicate.
_totalShares :: Total
, -- | A tagged sha256d hash of the complete ciphertext.
_crypttextHash :: CrypttextHash SHA256d
, -- | The root hash of a merkle tree where the leaf hashes are of segments of ciphertext.
_crypttextRootHash :: CrypttextHash SHA256d
, -- | The root hash of a merkle tree where leaf hashes are the root hashes of all of the block hash trees.
_shareRootHash :: CrypttextHash SHA256d
}
deriving (Eq, Ord, Generic, ToExpr)
$(makeLenses ''URIExtension)
instance Show URIExtension where
show (URIExtension name params tailParams sz segSize numSegs needed total hash1 hash2 hash3) =
T.unpack . T.concat $
[ "URIExtension { "
, "codec = "
, decodeLatin1 name
, "; codec-params = "
, showText params
, "; tail-codec-params = "
, showText tailParams
, "; size = "
, showText sz
, "; segment-size = "
, showText segSize
, "; num-segments = "
, showText numSegs
, "; needed-shares = "
, showText needed
, "; total-shares = "
, showText total
, "; crypttext-hash = "
, showText $ b32 hash1
, "; crypttext-root-hash = "
, showText $ b32 hash2
, "; share-root-hash = "
, showText $ b32 hash3
, " }"
]
where
showText :: Show s => s -> T.Text
showText = T.pack . show
b32 = encodeBase32Unpadded . toBytes
-- Serialize a URIExtension to bytes in the format it appears in a CHK share.
uriExtensionToBytes :: URIExtension -> B.ByteString
uriExtensionToBytes =
toWeirdString
-- all of the below values are authenticated by the capability you get when you store data in Tahoe
[ ("codec_name", view codecName)
, ("codec_params", paramsToBytes . view codecParams)
, ("tail_codec_params", paramsToBytes . view tailCodecParams)
, ("size", showBytes . view size)
, ("segment_size", showBytes . view segmentSize)
, ("num_segments", showBytes . view numSegments)
, ("needed_shares", showBytes . view neededShares)
, ("total_shares", showBytes . view totalShares)
, ("crypttext_hash", toBytes . view crypttextHash) -- hash of the *entire* cipher text
, ("crypttext_root_hash", toBytes . view crypttextRootHash) -- root hash of the *cipher text* merkle tree
, ("share_root_hash", toBytes . view shareRootHash) -- root hash of the *share* merkle tree
]
type Parser = Parsec Void B.ByteString
-- | A version of bounded specialized to parsing bytestrings.
bounded :: (Ord n, Integral n) => n -> n -> Parser n
bounded = Tahoe.CHK.Parsing.bounded decimal
{- | Parse the representation of a URIExtension which appears in CHK shares
back into a URIExtension.
-}
pURIExtension :: Parser URIExtension
pURIExtension =
runPermutation $
URIExtension
<$> toPermutation (B.pack <$> pField "codec_name" (`count` anySingle))
<*> toPermutation (pField "codec_params" $ const pParameters)
<*> toPermutation (pField "tail_codec_params" $ const pParameters)
<*> toPermutation (pField "size" $ const decimal)
<*> toPermutation (pField "segment_size" $ const decimal)
<*> toPermutation (pField "num_segments" $ const (bounded 1 maxBound))
<*> toPermutation (pField "needed_shares" $ const (bounded 1 256))
<*> toPermutation (pField "total_shares" $ const (bounded 1 256))
<*> toPermutation (pFieldM "crypttext_hash" pDigest)
<*> toPermutation (pFieldM "crypttext_root_hash" pDigest)
<*> toPermutation (pFieldM "share_root_hash" pDigest)
{- | Parse the raw bytes of a hash algorithm digest back into a Digest'. The
parser succeeds if exactly the size of the digest exactly matches the
specified number of tokens to parse.
-}
pDigest :: HashAlgorithm hash => Int -> Parser (Maybe (Digest' hash))
pDigest = takeP Nothing >=> (pure . (Digest' <$>) . digestFromByteString)
-- | Parse one field of a serialized URIExtension.
pField ::
-- | The serialized label for the field.
B.ByteString ->
-- | A function that takes the length of the field value and returns a parser for the field value.
(Int -> Parser a) ->
-- | A parser for the field.
Parser a
pField label pInner = do
void $ string (label <> ":")
len <- decimal -- XXX Could overflow
void $ string ":"
result <- pInner len
void $ string ","
pure result
{- | Flatten a Parser for a value in Maybe to a Parser for just the value. A
Nothing result from the inner parser will trigger a Parser error.
-}
pFieldM :: B.ByteString -> (Int -> Parser (Maybe a)) -> Parser a
pFieldM label pInner = do
result <- pField label pInner
case result of
Nothing -> fail $ "parsing " <> show label <> " failed to produce a value"
Just r -> pure r
-- | Serialize some named URIExtension fields to bytes.
toWeirdString ::
-- | A list of pairs of field names and functions to get serialized
-- field values.
[(B.ByteString, URIExtension -> B.ByteString)] ->
-- | The URIExtension to get the field values from.
URIExtension ->
-- | The concatenation of all of the serialized fields.
B.ByteString
toWeirdString fields ext =
B.concat . join . sort $ map (encodedField ext) fields
where
encodedField ext' (name, extract) =
[name, ":", netstring (extract ext')]
-- | Show a value as a UTF-8-encoded byte string.
showBytes :: (Show s) => s -> B.ByteString
showBytes = encodeUtf8 . T.pack . show
{- | Serialize Parameters to a byte string in the format it appears within the
URI extension block in a CHK share.
-}
paramsToBytes :: Parameters -> B.ByteString
paramsToBytes Parameters{paramSegmentSize, paramTotalShares, paramRequiredShares} =
B.concat [showBytes paramSegmentSize, "-", showBytes paramRequiredShares, "-", showBytes paramTotalShares]
{- | Parse a serialized Parameters value in the format produced by
paramsToBytes.
-}
pParameters :: Parser Parameters
pParameters =
(\segSize required total -> Parameters{paramSegmentSize = segSize, paramRequiredShares = required, paramHappyShares = 1, paramTotalShares = total})
<$> decimal
<* string "-"
<*> bounded 1 maxShares
<* string "-"
<*> bounded 1 maxShares
where
maxShares = 256