lorentz-0.13.1: src/Lorentz/Bytes.hs
-- SPDX-FileCopyrightText: 2020 Tocqueville Group
--
-- SPDX-License-Identifier: LicenseRef-MIT-TQ
{-# LANGUAGE NoPolyKinds #-}
-- | Type-safe operations with @bytes@-like data.
module Lorentz.Bytes
( BytesLike (..)
-- * Packed
, Packed (..)
-- * Signatures
, TSignature (..)
, lSign
-- * Hashes
, Hash (..)
, DHashAlgorithm
, KnownHashAlgorithm (..)
, toHashHs
, Sha256
, Sha512
, Blake2b
, Sha3
, Keccak
-- * Typed Chest
, ChestT (..)
, OpenChestT (..)
, openChestT
) where
import Crypto.Random (MonadRandom)
import Fmt (Buildable(..))
import Morley.Util.Markdown
import qualified Type.Reflection as Refl
import Lorentz.Annotation
import Lorentz.Base
import Lorentz.Constraints.Scopes
import Lorentz.Doc
import Lorentz.Value
import qualified Morley.Michelson.Typed as T
import Morley.Tezos.Crypto
import qualified Morley.Tezos.Crypto.Hash as Crypto
-- | Everything which is represented as bytes inside.
class (KnownValue bs, ToT bs ~ ToT ByteString) => BytesLike bs where
toBytes :: bs -> ByteString
instance BytesLike ByteString where
toBytes = id
----------------------------------------------------------------------------
-- Packing
----------------------------------------------------------------------------
-- | Represents a 'ByteString' resulting from packing a value of type @a@.
--
-- This is /not/ guaranteed to keep some packed value, and @unpack@ can fail.
-- We do so because often we need to accept values of such type from user,
-- and also because there is no simple way to check validity of packed data
-- without performing full unpack.
-- So this wrapper is rather a hint for users.
newtype Packed a = Packed { unPacked :: ByteString }
deriving stock (Show, Eq, Ord, Generic)
deriving newtype (IsoValue, HasAnnotation, BytesLike)
instance Buildable (Packed a) where
build = build . toVal
instance TypeHasDoc a => TypeHasDoc (Packed a) where
typeDocMdDescription = [md|
Packed value of the given type.
This exactly matches the result of Michelson `PACK` instruction application
to the given value.
|]
typeDocMdReference = poly1TypeDocMdReference
typeDocDependencies p =
genericTypeDocDependencies p <>
[ dTypeDep @a
, dTypeDep @MText, dTypeDep @Integer -- for examples below
]
typeDocHaskellRep = concreteTypeDocHaskellRep @(Packed (MText, Integer))
typeDocMichelsonRep = concreteTypeDocMichelsonRep @(Packed (MText, Integer))
----------------------------------------------------------------------------
-- Signatures
----------------------------------------------------------------------------
-- | Represents a signature, where signed data has given type.
--
-- Since we usually sign a packed data, a common pattern for this type is
-- @TSignature ('Packed' signedData)@.
-- If you don't want to use 'Packed', use plain @TSignature ByteString@ instead.
newtype TSignature a = TSignature { unTSignature :: Signature }
deriving stock (Show, Generic)
deriving newtype (IsoValue, HasAnnotation)
instance Buildable (TSignature a) where
build = build . toVal
instance TypeHasDoc a => TypeHasDoc (TSignature a) where
typeDocMdDescription = "Signature for data of the given type."
typeDocMdReference = poly1TypeDocMdReference
typeDocDependencies p =
genericTypeDocDependencies p <>
[ dTypeDep @a
, dTypeDep @MText, dTypeDep @Integer -- for examples below
]
typeDocHaskellRep = concreteTypeDocHaskellRep @(TSignature (MText, Integer))
typeDocMichelsonRep = concreteTypeDocMichelsonRep @(TSignature (MText, Integer))
-- | Sign data using 'SecretKey'
lSign :: (MonadRandom m, BytesLike a) => SecretKey -> a -> m (TSignature a)
lSign sk (toBytes -> bs) = TSignature <$> sign sk bs
----------------------------------------------------------------------------
-- Hashes
----------------------------------------------------------------------------
-- | Open kind for hash algorithms, to make it more difficult to apply type
-- arguments incorrectly.
type HashAlgorithmKind = HashAlgoTag -> Type
data HashAlgoTag
-- | Hash of type @t@ evaluated from data of type @a@.
newtype Hash (alg :: HashAlgorithmKind) a = UnsafeHash { unHash :: ByteString }
deriving stock (Show, Eq, Ord, Generic)
deriving newtype (IsoValue, HasAnnotation, BytesLike)
instance Buildable (Hash alg a) where
build = build . toVal
instance (KnownHashAlgorithm alg, TypeHasDoc a) => TypeHasDoc (Hash alg a) where
typeDocMdDescription = [md|
Hash of a value.
First type argument denotes algorithm used to compute the hash, and the second
argument describes the data being hashed.
|]
typeDocMdReference tp wp =
T.applyWithinParens wp $ mconcat
[ mdLocalRef (mdTicked "Hash") (docItemRef (DType tp))
, " "
, hashAlgorithmMdRef (Proxy @alg)
, " "
, typeDocMdReference (Proxy @a) (T.WithinParens True)
]
typeDocDependencies p =
genericTypeDocDependencies p <>
[ SomeDocDefinitionItem (DHashAlgorithm (Proxy @alg)), dTypeDep @a
, SomeDocDefinitionItem (DHashAlgorithm (Proxy @Blake2b)), dTypeDep @ByteString
--- ^ for examples below
]
typeDocHaskellRep = concreteTypeDocHaskellRep @(Hash Blake2b ByteString)
typeDocMichelsonRep = concreteTypeDocMichelsonRep @(Hash Blake2b ByteString)
-- | Hash algorithm used in Tezos.
class Typeable alg => KnownHashAlgorithm (alg :: HashAlgorithmKind) where
hashAlgorithmName :: Proxy alg -> Text
hashAlgorithmName _ =
toText . Refl.tyConName . Refl.typeRepTyCon $ Refl.typeRep @alg
computeHash :: ByteString -> ByteString
toHash :: BytesLike bs => bs : s :-> Hash alg bs : s
-- | Evaluate hash in Haskell world.
toHashHs
:: forall alg bs. (BytesLike bs, KnownHashAlgorithm alg)
=> bs -> Hash alg bs
toHashHs = UnsafeHash . computeHash @alg . toBytes
-- | Documentation item for hash algorithms.
data DHashAlgorithm where
DHashAlgorithm :: KnownHashAlgorithm alg => Proxy alg -> DHashAlgorithm
instance Eq DHashAlgorithm where
a == b = (a `compare` b) == EQ
instance Ord DHashAlgorithm where
DHashAlgorithm a `compare` DHashAlgorithm b =
hashAlgorithmName a `compare` hashAlgorithmName b
instance DocItem DHashAlgorithm where
type DocItemPlacement DHashAlgorithm = 'DocItemInDefinitions
type DocItemReferenced DHashAlgorithm = 'True
docItemPos = 5310
docItemSectionName = Just "Referenced hash algorithms"
docItemRef (DHashAlgorithm alg) =
DocItemRef $ DocItemId ("hash-alg-" <> hashAlgorithmName alg)
docItemToMarkdown _ (DHashAlgorithm alg) =
"* " <> build (hashAlgorithmName alg)
-- Creates a reference to given hash algorithm description.
hashAlgorithmMdRef :: KnownHashAlgorithm alg => Proxy alg -> Markdown
hashAlgorithmMdRef alg =
mdLocalRef
(mdTicked . build $ hashAlgorithmName alg)
(docItemRef (DHashAlgorithm alg))
data Sha256 :: HashAlgorithmKind
instance KnownHashAlgorithm Sha256 where
computeHash = Crypto.sha256
toHash = I T.SHA256
data Sha512 :: HashAlgorithmKind
instance KnownHashAlgorithm Sha512 where
computeHash = Crypto.sha512
toHash = I T.SHA512
data Blake2b :: HashAlgorithmKind
instance KnownHashAlgorithm Blake2b where
computeHash = Crypto.blake2b
toHash = I T.BLAKE2B
data Sha3 :: HashAlgorithmKind
instance KnownHashAlgorithm Sha3 where
computeHash = Crypto.sha3
toHash = I T.SHA3
data Keccak :: HashAlgorithmKind
instance KnownHashAlgorithm Keccak where
computeHash = Crypto.keccak
toHash = I T.KECCAK
----------------------------------------------------------------------------
-- Typed Chest
----------------------------------------------------------------------------
newtype ChestT a = ChestT { unChestT :: Chest }
deriving newtype (IsoValue, HasAnnotation)
deriving stock Generic
instance TypeHasDoc a => TypeHasDoc (ChestT a) where
typeDocMdDescription = [md|
Timelock puzzle chest containing a typed value.
In Lorentz, use `openChestT` instead of `openChest` to open it.
|]
typeDocMdReference = poly1TypeDocMdReference
typeDocDependencies p =
genericTypeDocDependencies p <>
[ dTypeDep @a
, dTypeDep @MText, dTypeDep @Integer -- for examples below
]
typeDocHaskellRep = concreteTypeDocHaskellRep @(ChestT (Packed (MText, Integer)))
typeDocMichelsonRep = concreteTypeDocMichelsonRep @(ChestT (Packed (MText, Integer)))
data OpenChestT a = ChestContentT a | ChestOpenFailedT Bool
deriving stock (Generic, Show, Eq)
deriving anyclass (T.IsoValue, HasAnnotation)
instance (TypeHasDoc a) => TypeHasDoc (OpenChestT a) where
typeDocMdDescription = "Typed result of opening a typed timelocked chest."
typeDocMdReference = poly1TypeDocMdReference
typeDocDependencies _ =
[ dTypeDep @a
, dTypeDep @MText, dTypeDep @Integer -- for examples below
]
typeDocHaskellRep _ _ = Nothing
typeDocMichelsonRep = concreteTypeDocMichelsonRep @(OpenChestT (Packed (MText, Integer)))
openChestT :: BytesLike a => ChestKey : ChestT a : Natural : s :-> OpenChestT a : s
openChestT = I T.OPEN_CHEST