project-m36-0.9.8: src/lib/ProjectM36/HashSecurely.hs
-- | A unified class for walking the database structure to produce a hash used for Merkle trees and validation.
{-# LANGUAGE FlexibleInstances, GeneralizedNewtypeDeriving, RankNTypes, ExistentialQuantification, BangPatterns #-}
module ProjectM36.HashSecurely where
import qualified Data.ByteString.Lazy as BL
import qualified Data.ByteString as B
import qualified Data.ByteString.Builder as BSB
import qualified Data.ByteString.Builder.Scientific as BSB
import ProjectM36.Base
import ProjectM36.Tuple (tupleAttributes, tupleAtoms)
import ProjectM36.Serialise.Base ()
import ProjectM36.IsomorphicSchema
import ProjectM36.Transaction
import qualified Data.HashSet as HS
import qualified ProjectM36.DataConstructorDef as DC
import ProjectM36.MerkleHash
import Data.List (sortOn)
import qualified Data.Map as M
import qualified ProjectM36.TypeConstructorDef as TCons
import qualified Crypto.Hash.SHA256 as SHA256
import qualified Data.Text.Encoding as TE
import qualified Data.Text as T
import qualified Data.UUID as UUID
import qualified Data.Vector as V
import qualified Data.Set as S
import Data.Time.Calendar
import Data.Time.Clock
import Codec.Winery (Serialise)
newtype SecureHash = SecureHash { _unSecureHash :: B.ByteString }
deriving (Serialise, Show, Eq)
-- run a SHA256 hasher across the necessary data structures
class HashBytes a where
hashBytes :: a -> SHA256.Ctx -> SHA256.Ctx
instance HashBytes Atom where
hashBytes atm ctx =
case atm of
IntegerAtom i -> up ("IntegerAtom" <> build (BSB.integerDec i))
IntAtom i -> up ("IntAtom" <> build (BSB.intDec i))
ScientificAtom s -> up ("ScientificAtom" <> build (BSB.scientificBuilder s))
DoubleAtom d -> up ("DoubleAtom" <> build (BSB.doubleDec d))
TextAtom t -> up ("TextAtom" <> TE.encodeUtf8 t)
DayAtom d -> up ("DayAtom" <> build (BSB.integerDec (toModifiedJulianDay d)))
DateTimeAtom dt -> up ("DateTimeAtom" <>
build (BSB.integerDec (toModifiedJulianDay (utctDay dt)) <>
BSB.integerDec (diffTimeToPicoseconds (utctDayTime dt))))
ByteStringAtom bs -> up ("ByteStringAtom" <> bs)
BoolAtom b -> up ("BoolAtom" <> if b then "1" else "0")
UUIDAtom u -> up ("UUIDAtom" <> BL.toStrict (UUID.toByteString u))
RelationAtom r -> hashBytesL ctx "RelationAtom" [SHash r]
RelationalExprAtom e -> hashBytesL ctx "RelationalExprAtom" [SHash e]
ConstructedAtom d typ args ->
hashBytesL ctx "ConstructedAtom" ([SHash d, SHash typ] <> map SHash args)
where
build = BL.toStrict . BSB.toLazyByteString
up = SHA256.update ctx
instance HashBytes T.Text where
hashBytes t ctx = SHA256.update ctx (TE.encodeUtf8 t)
instance HashBytes Relation where
hashBytes (Relation attrs tupSet) ctx =
hashBytesL ctx "Relation" [SHash attrs, SHash tupSet]
data SHash = forall a. HashBytes a => SHash !a
hashBytesL :: Foldable f => SHA256.Ctx -> B.ByteString -> f SHash -> SHA256.Ctx
hashBytesL ctx name = foldr (\(SHash i) ctx'@(SHA256.Ctx !bs) -> bs `seq` hashBytes i ctx') (SHA256.update ctx name)
instance HashBytes a => HashBytes (RelationalExprBase a) where
hashBytes (MakeRelationFromExprs mAttrs tupleExprs) ctx =
hashBytesL ctx "MakeRelationFromExprs" [SHash mAttrs, SHash tupleExprs]
hashBytes (MakeStaticRelation attrs tupSet) ctx = -- blowing up here!
hashBytesL ctx "MakeStaticRelation" [SHash attrs, SHash tupSet]
-- hashBytes _ ctx = ctx
hashBytes (ExistingRelation (Relation attrs tupSet)) ctx =
hashBytesL ctx "ExistingRelation" [SHash tupSet, SHash attrs]
hashBytes (RelationVariable rvName marker) ctx =
hashBytesL ctx "RelationVariable" [SHash rvName, SHash marker]
hashBytes (Project attrNames expr) ctx =
hashBytesL ctx "Project" [SHash attrNames, SHash expr]
hashBytes (Union exprA exprB) ctx =
hashBytesL ctx "Union" [SHash exprA, SHash exprB]
hashBytes (Join exprA exprB) ctx =
hashBytesL ctx "Join" [SHash exprA, SHash exprB]
hashBytes (Rename nameA nameB expr) ctx =
hashBytesL ctx "Rename" [SHash nameA, SHash nameB, SHash expr]
hashBytes (Difference exprA exprB) ctx =
hashBytesL ctx "Difference" [SHash exprA, SHash exprB]
hashBytes (Group names name expr) ctx =
hashBytesL ctx "Group" [SHash names, SHash name, SHash expr]
hashBytes (Ungroup name expr) ctx =
hashBytesL ctx "Ungroup" [SHash name, SHash expr]
hashBytes (Restrict pred' expr) ctx =
hashBytesL ctx "Restrict" [SHash pred', SHash expr]
hashBytes (Equals exprA exprB) ctx =
hashBytesL ctx "Equals" [SHash exprA, SHash exprB]
hashBytes (NotEquals exprA exprB) ctx =
hashBytesL ctx "NotEquals" [SHash exprA, SHash exprB]
hashBytes (Extend ext expr) ctx =
hashBytesL ctx "Extend" [SHash ext, SHash expr]
hashBytes (With withExprs expr) ctx =
hashBytesL ctx "With" (SHash expr: map SHash (sortOn (\(WithNameExpr rv _, _) -> rv) withExprs))
instance HashBytes a => HashBytes (AttributeNamesBase a) where
hashBytes (AttributeNames s) ctx = hashBytesL ctx "AttributeNames" (map SHash (S.toAscList s))
hashBytes (InvertedAttributeNames s) ctx = hashBytesL ctx "InvertedAttributeNames" (map SHash (S.toAscList s))
hashBytes (UnionAttributeNames a b) ctx = hashBytesL ctx "UnionAttributeNames" [SHash a, SHash b]
hashBytes (IntersectAttributeNames a b) ctx = hashBytesL ctx "IntersectAttributeNames" [SHash a, SHash b]
hashBytes (RelationalExprAttributeNames r) ctx = hashBytesL ctx "RelationalExprAttributeNames" [SHash r]
instance HashBytes a => HashBytes (ExtendTupleExprBase a) where
hashBytes (AttributeExtendTupleExpr name expr) ctx =
hashBytesL ctx "AttributeExtendTupleExpr" [SHash name, SHash expr]
instance HashBytes a => HashBytes (WithNameExprBase a) where
hashBytes (WithNameExpr rv marker) ctx = hashBytesL ctx "WithNameExpr" [SHash rv, SHash marker]
instance HashBytes GraphRefTransactionMarker where
hashBytes (TransactionMarker tid) ctx = SHA256.update ctx (BL.toStrict ("TransactionMarker" <> UUID.toByteString tid))
hashBytes UncommittedContextMarker ctx = SHA256.update ctx "UncommittedContextMarker"
instance HashBytes a => HashBytes (TupleExprBase a) where
hashBytes (TupleExpr exprMap) ctx =
foldr (\(attrName, atomExpr) ctx' ->
hashBytesL ctx' "TupleExpr" [SHash attrName, SHash atomExpr])
ctx (M.toAscList exprMap)
instance HashBytes a => HashBytes (AtomExprBase a) where
hashBytes atomExpr ctx =
case atomExpr of
(AttributeAtomExpr a) -> hashBytesL ctx "AttributeAtomExpr" [SHash a]
(NakedAtomExpr a) -> hashBytesL ctx "NakedAtomExpr" [SHash a]
(FunctionAtomExpr fname args marker) ->
hashBytesL ctx "FunctionAtomExpr" $ [SHash fname, SHash marker] <> map SHash args
(RelationAtomExpr r) -> hashBytesL ctx "RelationAtomExpr" [SHash r]
(ConstructedAtomExpr dConsName args marker) ->
hashBytesL ctx "ConstructedAtomExpr" ([SHash dConsName, SHash marker] <> map SHash args)
instance HashBytes () where
hashBytes () ctx = SHA256.update ctx "()"
instance HashBytes AtomType where
hashBytes typ ctx =
case typ of
IntAtomType -> hashb "IntAtomType"
IntegerAtomType -> hashb "IntegerAtomType"
ScientificAtomType -> hashb "ScientificAtomType"
DoubleAtomType -> hashb "DoubleAtomType"
TextAtomType -> hashb "TextAtomType"
DayAtomType -> hashb "DayAtomType"
DateTimeAtomType -> hashb "DateTimeAtomType"
ByteStringAtomType -> hashb "ByteStringAtomType"
BoolAtomType -> hashb "BoolAtomType"
UUIDAtomType -> hashb "UUIDAtomType"
RelationAtomType attrs -> hashBytesL ctx "RelationAtomType" (V.map SHash (attributesVec attrs))
ConstructedAtomType tConsName tvarMap -> hashBytesL ctx "ConstructedAtomType" (SHash tConsName : map SHash (M.toAscList tvarMap))
RelationalExprAtomType -> hashb "RelationalExprAtomType"
TypeVariableType tvn -> hashBytesL ctx "TypeVariableType" [SHash tvn]
where
hashb = SHA256.update ctx
instance HashBytes Attributes where
hashBytes attrs ctx =
hashBytesL ctx "Attributes" (V.map SHash (attributesVec attrs))
instance HashBytes RelationTupleSet where
hashBytes tupSet ctx =
hashBytesL ctx "RelationTupleSet" (map SHash (asList tupSet))
instance HashBytes a => HashBytes (Maybe [AttributeExprBase a]) where
hashBytes Nothing ctx = SHA256.update ctx "MaybeAttributeExprBaseNothing"
hashBytes (Just exprs) ctx =
hashBytesL ctx "MaybeAttributeExprBase" (map SHash exprs)
instance HashBytes a => HashBytes (TupleExprsBase a) where
hashBytes (TupleExprs marker tupleExprs) ctx =
hashBytesL ctx "TupleExprs" (SHash marker : map SHash tupleExprs)
instance HashBytes Attribute where
hashBytes (Attribute name typ) ctx =
hashBytesL ctx "Attribute" [SHash name, SHash typ]
instance (HashBytes a, HashBytes b) => HashBytes (a, b) where
hashBytes (a,b) ctx =
hashBytesL ctx "HTuple" [SHash a, SHash b]
instance HashBytes RelationTuple where
hashBytes tup ctx =
hashBytesL ctx "RelationTuple" (V.cons (SHash (tupleAttributes tup)) (V.map SHash (tupleAtoms tup)))
instance HashBytes a => HashBytes (AttributeExprBase a) where
hashBytes (AttributeAndTypeNameExpr aname tcons marker) ctx =
hashBytesL ctx "AttributeAndTypeNameExpr" [SHash aname, SHash tcons, SHash marker]
hashBytes (NakedAttributeExpr attr) ctx =
hashBytesL ctx "NakedAttributeExpr" [SHash attr]
instance HashBytes TypeConstructor where
hashBytes tcons ctx =
case tcons of
ADTypeConstructor tName args ->
hashBytesL ctx "ADTypeConstructor" (SHash tName : map SHash args)
PrimitiveTypeConstructor tConsName typ ->
hashBytesL ctx "PrimitiveTypeConstructor" [SHash tConsName, SHash typ]
RelationAtomTypeConstructor attrExprs ->
hashBytesL ctx "RelationAtomTypeConstructor" (map SHash attrExprs)
TypeVariable tv ->
hashBytesL ctx "TypeVariable" [SHash tv]
instance HashBytes TransactionId where
hashBytes tid ctx = SHA256.update ctx ("TransactionId" <> BL.toStrict (UUID.toByteString tid))
instance HashBytes Schema where
hashBytes (Schema morphs) ctx =
hashBytesL ctx "Schema" (map SHash (sortOn sortIso morphs))
where
sortIso iso = mconcat (isomorphInRelVarNames iso)
instance HashBytes SchemaIsomorph where
hashBytes (IsoRestrict r p (a,b)) ctx =
hashBytesL ctx "IsoRestrict" [SHash r, SHash p, SHash a, SHash b]
hashBytes (IsoRename a b) ctx =
hashBytesL ctx "IsoRename" [SHash a, SHash b]
hashBytes (IsoUnion (a,b) p r) ctx =
hashBytesL ctx "IsoUnion" [SHash a, SHash b, SHash p, SHash r]
instance HashBytes a => HashBytes (RestrictionPredicateExprBase a) where
hashBytes TruePredicate ctx = SHA256.update ctx "TruePredicate"
hashBytes (AndPredicate a b) ctx = hashBytesL ctx "AndPredicate" [SHash a, SHash b]
hashBytes (OrPredicate a b) ctx = hashBytesL ctx "OrPredicate" [SHash a, SHash b]
hashBytes (NotPredicate a) ctx = hashBytesL ctx "NotPredicate" [SHash a]
hashBytes (RelationalExprPredicate e) ctx = hashBytesL ctx "RelationalExprPredicate" [SHash e]
hashBytes (AtomExprPredicate a) ctx = hashBytesL ctx "AtomExprPredicate" [SHash a]
hashBytes (AttributeEqualityPredicate a e) ctx = hashBytesL ctx "AttributeEqualityPredicate" [SHash a, SHash e]
instance HashBytes MerkleHash where
hashBytes h ctx =
SHA256.update ctx (_unMerkleHash h)
instance HashBytes UTCTime where
hashBytes tim ctx =
SHA256.update ctx (BL.toStrict ("UTCTime" <>
BSB.toLazyByteString (BSB.integerDec (toModifiedJulianDay (utctDay tim))) <>
BSB.toLazyByteString (BSB.integerDec (diffTimeToPicoseconds (utctDayTime tim)))))
instance HashBytes DatabaseContext where
hashBytes db ctx =
hashBytesL ctx "DatabaseContext" [SHash (inclusionDependencies db),
SHash (relationVariables db),
SHash (notifications db),
SHash (typeConstructorMapping db),
SHash (atomFunctions db),
SHash (dbcFunctions db)]
instance HashBytes InclusionDependencies where
hashBytes incDeps ctx =
hashBytesL ctx "InclusionDependencies" (map SHash (M.toAscList incDeps))
instance HashBytes RelationVariables where
hashBytes rvs ctx =
hashBytesL ctx "RelationVariables" (map SHash (M.toAscList rvs))
instance HashBytes Notifications where
hashBytes nots ctx =
hashBytesL ctx "Notifications" (map SHash (M.toAscList nots))
instance HashBytes TypeConstructorMapping where
hashBytes tConsMap ctx =
hashBytesL ctx "TypeConstructorMapping" (map SHash (sortOn (TCons.name . fst) tConsMap))
instance HashBytes AtomFunctions where
hashBytes afuncs ctx =
hashBytesL ctx "AtomFunctions" (map SHash (sortOn funcName (HS.toList afuncs)))
instance HashBytes AtomFunction where
hashBytes func ctx =
hashBytesL ctx "AtomFunction" (SHash (funcName func):
SHash (funcBody func):
map SHash (funcType func))
instance HashBytes DatabaseContextFunction where
hashBytes func ctx =
hashBytesL ctx "DatabaseContextFunction" (SHash (funcName func):
SHash (funcBody func):
map SHash (funcType func))
instance HashBytes DatabaseContextFunctions where
hashBytes dbcfuncs ctx =
hashBytesL ctx "DatabaseContextFunctions" (map SHash (sortOn funcName (HS.toList dbcfuncs)))
instance HashBytes InclusionDependency where
hashBytes (InclusionDependency exprA exprB) ctx =
hashBytesL ctx "InclusionDependency" [SHash exprA, SHash exprB]
instance HashBytes Notification where
hashBytes notif ctx =
hashBytesL ctx "Notification" [SHash (changeExpr notif),
SHash (reportOldExpr notif),
SHash (reportNewExpr notif)]
instance HashBytes DataConstructorDef where
hashBytes (DataConstructorDef dConsName args) ctx =
hashBytesL ctx "DataConstructorDef" (SHash dConsName : map SHash args)
instance HashBytes [DataConstructorDef] where
hashBytes defs ctx =
hashBytesL ctx "DataConstructoDefList" (map SHash (sortOn DC.name defs))
instance HashBytes TypeConstructorDef where
hashBytes (ADTypeConstructorDef tCons args) ctx =
hashBytesL ctx "ADTypeConstructorDef" (SHash tCons: map SHash args)
hashBytes (PrimitiveTypeConstructorDef tCons typ) ctx =
hashBytesL ctx "PrimitiveTypeConstructorDef" [SHash tCons, SHash typ]
instance HashBytes (FunctionBody a) where
hashBytes (FunctionScriptBody s _) ctx = hashBytesL ctx "FunctionScriptBody" [SHash s]
hashBytes (FunctionBuiltInBody _) ctx = SHA256.update ctx "FunctionBuiltInBody"
hashBytes (FunctionObjectLoadedBody a b c _) ctx = hashBytesL ctx "FunctionObjectLoadedBody" (map (SHash . T.pack) [a,b,c])
instance HashBytes DataConstructorDefArg where
hashBytes (DataConstructorDefTypeConstructorArg tCons) ctx =
hashBytesL ctx "DataConstructorDefTypeConstructorArg" [SHash tCons]
hashBytes (DataConstructorDefTypeVarNameArg tv) ctx =
hashBytesL ctx "DataConstructorDefTypeVarNameArg" [SHash tv]
instance HashBytes (M.Map RelVarName Relation) where
hashBytes m ctx =
hashBytesL ctx "rvtypes" (map SHash (M.toAscList m))
-- | Hash a transaction within its graph context to create a Merkle hash for it.
hashTransaction :: Transaction -> S.Set Transaction -> MerkleHash
hashTransaction trans parentTranses = MerkleHash (SHA256.finalize newHash)
where
newHash = hashBytesL SHA256.init "Transaction" (map SHash transIds <>
map SHash (M.toAscList (subschemas trans)) <>
map SHash parentMerkleHashes <>
[SHash tstamp,
SHash (concreteDatabaseContext trans)]
)
tstamp = stamp (transactionInfo trans)
parentMerkleHashes = map getMerkleHash (S.toAscList parentTranses)
getMerkleHash t = merkleHash (transactionInfo t)
transIds = transactionId trans : S.toAscList (parentIds trans)
-- | Return a hash of just DDL-specific (schema) attributes. This is useful for determining if a client has the appropriate updates needed to work with the current schema.
mkDDLHash :: DatabaseContext -> M.Map RelVarName Relation -> SecureHash
mkDDLHash ctx rvtypemap = do
-- we cannot merely hash the relational representation of the type because the order of items matters when hashing
-- registered queries are not included here because a client could be compatible with a schema even if the queries are not registered. The client should validate registered query state up-front. Perhaps there should be another hash for registered queries.
SecureHash $ SHA256.finalize $ hashBytesL SHA256.init "DDLHash" [SHash (inclusionDependencies ctx),
SHash (atomFunctions ctx),
SHash (dbcFunctions ctx),
SHash (typeConstructorMapping ctx),
SHash rvtypemap]