packages feed

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