haskell-opentimestamps-0.5.5.0: src/OpenTimestamps/DetachedTimestampFile.hs
{-# LANGUAGE GADTs #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE OverloadedStrings #-}
{- | Detached timestamp file handling for OpenTimestamps.
This module provides functionality for working with detached timestamp
files (.ots format), including serialization, deserialization,
and digest type handling for different hash algorithms.
-}
module OpenTimestamps.DetachedTimestampFile
( DetachedTimestampFile (..)
, DigestType (..)
, digestLen
, fromTag
, toTag
, serialize
, deserialize
) where
import Control.Monad (unless)
import Data.Binary.Get
( Get
, getByteString
, getWord8
, runGetOrFail
)
import Data.Binary.Put (Put, putByteString, putWord8, runPut)
import qualified Data.ByteString as BS
import Data.Word (Word8)
import OpenTimestamps.Config as Config
import qualified OpenTimestamps.Timestamp as TS
import OpenTimestamps.Types (OTsByteStream)
import OpenTimestamps.VarInt (getVarInt, putVarInt)
-- | Magic bytes that every proof must start with
magic :: BS.ByteString
magic = Config.detachedTimestampFileMagic
-- | Major version of timestamp files
version :: Int
version = fromIntegral Config.detachedTimestampFileVersion
{- | Structure representing a detached timestamp file.
Contains the hash function used to create the document digest
and the timestamp proving when that digest existed.
-}
data DetachedTimestampFile where
DetachedTimestampFile ::
{ digestType :: DigestType
, timestamp :: TS.Timestamp
} ->
DetachedTimestampFile
deriving (Eq, Show)
-- | Type of hash function used to produce the document digest.
data DigestType where
DSha1 :: DigestType
DSha256 :: DigestType
DRipemd160 :: DigestType
deriving (Eq, Show, Ord)
-- | Interpret a one-byte tag as a digest type.
fromTag :: Word8 -> Either String DigestType
fromTag 0x02 = Right DSha1
fromTag 0x03 = Right DRipemd160
fromTag 0x08 = Right DSha256
fromTag x = Left ("BadDigestTag: " ++ show x)
-- | Serialize a digest type to its one-byte tag.
toTag :: DigestType -> Word8
toTag DSha1 = 0x02
toTag DSha256 = 0x08
toTag DRipemd160 = 0x03
-- | The length, in bytes, that a digest with this hash function will be.
digestLen :: DigestType -> Int
digestLen DSha1 = 20
digestLen DSha256 = 32
digestLen DRipemd160 = 20
-- | Read magic number and verify it matches the expected format.
readMagic :: Get ()
readMagic = do
m <- getByteString (BS.length magic)
unless (m == magic) $
fail $
"Bad magic:\nAPI:\t " ++ show m ++ "\nmagic:\t " ++ show magic ++ ")"
-- | Read version number and verify it matches the expected format.
readVersion :: Get ()
readVersion = do
v <- getVarInt
unless (v == version) $ fail ("Bad version: " ++ show v)
-- | Deserialize a detached timestamp file from binary format.
getDetachedTimestampFile :: Get DetachedTimestampFile
getDetachedTimestampFile = do
readMagic
readVersion
tag <- getWord8
digestType' <- case fromTag tag of
Left err -> fail err
Right dt -> pure dt
let len = digestLen digestType'
digest <- getByteString len
ts <- TS.deserialize digest
pure $ DetachedTimestampFile digestType' ts
-- | Deserialize a detached timestamp file from a byte stream.
deserialize ::
OTsByteStream ->
Either String DetachedTimestampFile
deserialize bs = case runGetOrFail getDetachedTimestampFile bs of
Left (_, _, err) -> Left err
Right (_, _, val) -> Right val
-- | Write the magic bytes to the output.
putMagic :: Put
putMagic = putByteString magic
-- | Write the version number to the output.
putVersion :: Put
putVersion = putVarInt version
-- | Serialize a detached timestamp file to binary format.
putDetachedTimestampFile :: DetachedTimestampFile -> Put
putDetachedTimestampFile dtf = do
putMagic
putVersion
putWord8 $ toTag dtf.digestType
putByteString $ TS.timestampMsg dtf.timestamp
TS.putTimestamp dtf.timestamp
-- | Serialize a detached timestamp file to a byte stream.
serialize :: DetachedTimestampFile -> OTsByteStream
serialize = runPut . putDetachedTimestampFile