From 3bdff9ae3a3e40cdd76caf54dee414d8e157630f Mon Sep 17 00:00:00 2001 From: Ziyang Liu Date: Sat, 11 Nov 2023 21:57:58 -0800 Subject: [PATCH] Add `Hashable` instances for UPLC `Term` and related types (#5629) --- .../20231111_160632_unsafeFixIO_hash.md | 3 +++ .../src/PlutusCore/Crypto/BLS12_381/G1.hs | 4 ++++ .../src/PlutusCore/Crypto/BLS12_381/G2.hs | 4 ++++ .../PlutusCore/Crypto/BLS12_381/Pairing.hs | 4 ++++ .../plutus-core/src/PlutusCore/Data.hs | 3 ++- .../src/PlutusCore/DeBruijn/Internal.hs | 9 +++++---- plutus-core/plutus-core/src/Universe/Core.hs | 13 +++++++++++++ .../src/UntypedPlutusCore/Core/Instance/Eq.hs | 19 +++++++++++++++++++ 8 files changed, 54 insertions(+), 5 deletions(-) create mode 100644 plutus-core/changelog.d/20231111_160632_unsafeFixIO_hash.md diff --git a/plutus-core/changelog.d/20231111_160632_unsafeFixIO_hash.md b/plutus-core/changelog.d/20231111_160632_unsafeFixIO_hash.md new file mode 100644 index 00000000000..f38cd3ca6db --- /dev/null +++ b/plutus-core/changelog.d/20231111_160632_unsafeFixIO_hash.md @@ -0,0 +1,3 @@ +### Added + +- `Hashable` instances for `Data`, UPLC `Term` and related types. diff --git a/plutus-core/plutus-core/src/PlutusCore/Crypto/BLS12_381/G1.hs b/plutus-core/plutus-core/src/PlutusCore/Crypto/BLS12_381/G1.hs index a1cc5563692..80d26058529 100644 --- a/plutus-core/plutus-core/src/PlutusCore/Crypto/BLS12_381/G1.hs +++ b/plutus-core/plutus-core/src/PlutusCore/Crypto/BLS12_381/G1.hs @@ -28,6 +28,7 @@ import Text.PrettyBy (PrettyBy) import Control.DeepSeq (NFData, rnf, rwhnf) import Data.ByteString (ByteString, length) import Data.Coerce (coerce) +import Data.Hashable import Data.Proxy (Proxy (..)) import Flat import Prettyprinter @@ -65,6 +66,9 @@ instance Flat Element where instance NFData Element where rnf (Element x) = rwhnf x -- Just to be on the safe side. +instance Hashable Element where + hashWithSalt salt = hashWithSalt salt . compress + -- | Add two G1 group elements {-# INLINE add #-} add :: Element -> Element -> Element diff --git a/plutus-core/plutus-core/src/PlutusCore/Crypto/BLS12_381/G2.hs b/plutus-core/plutus-core/src/PlutusCore/Crypto/BLS12_381/G2.hs index b0d1af96ba3..50ab6d98eeb 100644 --- a/plutus-core/plutus-core/src/PlutusCore/Crypto/BLS12_381/G2.hs +++ b/plutus-core/plutus-core/src/PlutusCore/Crypto/BLS12_381/G2.hs @@ -28,6 +28,7 @@ import Text.PrettyBy (PrettyBy) import Control.DeepSeq (NFData, rnf, rwhnf) import Data.ByteString (ByteString, length) import Data.Coerce (coerce) +import Data.Hashable import Data.Proxy (Proxy (..)) import Flat import Prettyprinter @@ -51,6 +52,9 @@ instance Flat Element where instance NFData Element where rnf (Element x) = rwhnf x -- Just to be on the safe side. +instance Hashable Element where + hashWithSalt salt = hashWithSalt salt . compress + -- | Add two G2 group elements {-# INLINE add #-} add :: Element -> Element -> Element diff --git a/plutus-core/plutus-core/src/PlutusCore/Crypto/BLS12_381/Pairing.hs b/plutus-core/plutus-core/src/PlutusCore/Crypto/BLS12_381/Pairing.hs index e93cf21ae74..57d404768ce 100644 --- a/plutus-core/plutus-core/src/PlutusCore/Crypto/BLS12_381/Pairing.hs +++ b/plutus-core/plutus-core/src/PlutusCore/Crypto/BLS12_381/Pairing.hs @@ -21,6 +21,7 @@ import Text.PrettyBy (PrettyBy, prettyBy) import Control.DeepSeq (NFData, rnf) import Data.Coerce (coerce) +import Data.Hashable import Flat import Prettyprinter @@ -49,6 +50,9 @@ instance Flat MlResult where instance NFData MlResult where rnf _ = () +instance Hashable MlResult where + hashWithSalt salt = const salt + millerLoop :: G1.Element -> G2.Element -> MlResult millerLoop = coerce BlstBindings.millerLoop diff --git a/plutus-core/plutus-core/src/PlutusCore/Data.hs b/plutus-core/plutus-core/src/PlutusCore/Data.hs index b3add36ed3e..7553fde0ccb 100644 --- a/plutus-core/plutus-core/src/PlutusCore/Data.hs +++ b/plutus-core/plutus-core/src/PlutusCore/Data.hs @@ -22,6 +22,7 @@ import Data.ByteString qualified as BS import Data.ByteString.Base64 qualified as Base64 import Data.ByteString.Lazy qualified as BSL import Data.Data qualified +import Data.Hashable import Data.Text.Encoding qualified as Text import Data.Word (Word64, Word8) import GHC.Generics @@ -44,7 +45,7 @@ data Data = | I Integer | B BS.ByteString deriving stock (Show, Eq, Ord, Generic, Data.Data.Data) - deriving anyclass (NFData, NoThunks) + deriving anyclass (Hashable, NFData, NoThunks) instance Pretty Data where pretty = \case diff --git a/plutus-core/plutus-core/src/PlutusCore/DeBruijn/Internal.hs b/plutus-core/plutus-core/src/PlutusCore/DeBruijn/Internal.hs index bdb445f2ed3..9aea04e13bf 100644 --- a/plutus-core/plutus-core/src/PlutusCore/DeBruijn/Internal.hs +++ b/plutus-core/plutus-core/src/PlutusCore/DeBruijn/Internal.hs @@ -57,6 +57,7 @@ import Control.Monad.Reader import Control.Monad.State import Data.Bimap qualified as BM +import Data.Hashable import Data.Map qualified as M import Data.Text qualified as T import Data.Word @@ -102,7 +103,7 @@ the Num and write `DeBruijn (Index -1)`. This can be revisited when we implement -} newtype Index = Index Word64 deriving stock (Generic) - deriving newtype (Show, Num, Enum, Real, Integral, Eq, Ord, Pretty, NFData, Read) + deriving newtype (Show, Num, Enum, Real, Integral, Eq, Ord, Hashable, Pretty, NFData, Read) -- | The LamAbs index (for debruijn indices) and the starting level of DeBruijn monad deBruijnInitIndex :: Index @@ -113,7 +114,7 @@ deBruijnInitIndex = 0 -- | A term name as a de Bruijn index. data NamedDeBruijn = NamedDeBruijn {ndbnString :: !T.Text, ndbnIndex :: !Index} deriving stock (Show, Generic, Read) - deriving anyclass (NFData) + deriving anyclass (Hashable, NFData) {-| A wrapper around `NamedDeBruijn` that *must* hold the invariant of name=`fakeName`. @@ -123,7 +124,7 @@ but injection `ND->FND` is unsafe, thus they are not isomorphic. See NOTE: [Why newtype FakeNamedDeBruijn] -} newtype FakeNamedDeBruijn = FakeNamedDeBruijn { unFakeNamedDeBruijn :: NamedDeBruijn } - deriving newtype (Show, Eq, NFData, PrettyBy config) + deriving newtype (Show, Eq, Hashable, NFData, PrettyBy config) toFake :: DeBruijn -> FakeNamedDeBruijn toFake (DeBruijn ix) = FakeNamedDeBruijn $ NamedDeBruijn fakeName ix @@ -142,7 +143,7 @@ instance Eq NamedDeBruijn where -- | A term name as a de Bruijn index, without the name string. newtype DeBruijn = DeBruijn {dbnIndex :: Index} deriving stock (Show, Generic, Eq) - deriving newtype (NFData) + deriving newtype (Hashable, NFData) -- | A type name as a de Bruijn index. newtype NamedTyDeBruijn = NamedTyDeBruijn NamedDeBruijn diff --git a/plutus-core/plutus-core/src/Universe/Core.hs b/plutus-core/plutus-core/src/Universe/Core.hs index ff229a19b99..ddca2bea674 100644 --- a/plutus-core/plutus-core/src/Universe/Core.hs +++ b/plutus-core/plutus-core/src/Universe/Core.hs @@ -60,6 +60,7 @@ import Data.Dependent.Sum import Data.GADT.Compare import Data.GADT.DeepSeq import Data.GADT.Show +import Data.Hashable import Data.Kind import Data.Proxy import Data.Some.Newtype @@ -793,3 +794,15 @@ instance Closed uni => NFData (SomeTypeIn uni) where instance (Closed uni, uni `Everywhere` NFData) => NFData (ValueOf uni a) where rnf = grnf + +instance (Closed uni, GEq uni) => Hashable (SomeTypeIn uni) where + hashWithSalt salt (SomeTypeIn uni) = hashWithSalt salt $ encodeUni uni + +instance (Closed uni, GEq uni, uni `Everywhere` Eq, uni `Everywhere` Hashable) => + Hashable (ValueOf uni a) where + hashWithSalt salt (ValueOf uni x) = + bring (Proxy @Hashable) uni $ hashWithSalt salt (SomeTypeIn uni, x) + +instance (Closed uni, GEq uni, uni `Everywhere` Eq, uni `Everywhere` Hashable) => + Hashable (Some (ValueOf uni)) where + hashWithSalt salt (Some s) = hashWithSalt salt s diff --git a/plutus-core/untyped-plutus-core/src/UntypedPlutusCore/Core/Instance/Eq.hs b/plutus-core/untyped-plutus-core/src/UntypedPlutusCore/Core/Instance/Eq.hs index c5265504c1d..380ea9f8100 100644 --- a/plutus-core/untyped-plutus-core/src/UntypedPlutusCore/Core/Instance/Eq.hs +++ b/plutus-core/untyped-plutus-core/src/UntypedPlutusCore/Core/Instance/Eq.hs @@ -1,6 +1,7 @@ -- editorconfig-checker-disable-file {-# OPTIONS_GHC -fno-warn-orphans #-} +{-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE UndecidableInstances #-} @@ -19,11 +20,23 @@ import PlutusCore.Rename.Monad import Universe import Data.Foldable (for_) +import Data.Hashable instance (GEq uni, Closed uni, uni `Everywhere` Eq, Eq fun, Eq ann) => Eq (Term Name uni fun ann) where term1 == term2 = runEqRename $ eqTermM term1 term2 +type HashableTermConstraints uni fun ann = + ( GEq uni + , Closed uni + , uni `Everywhere` Eq + , uni `Everywhere` Hashable + , Hashable ann + , Hashable fun + ) + +instance HashableTermConstraints uni fun ann => Hashable (Term Name uni fun ann) + -- Simple Structural Equality of a `Term NamedDeBruijn`. This implies three things: -- a) We ignore the name part of the nameddebruijn -- b) We do not do equality "modulo init index, see deBruijnInitIndex". E.g. `LamAbs 0 (Var 0) /= LamAbs 1 (Var 1)`. @@ -33,14 +46,20 @@ deriving stock instance (GEq uni, Closed uni, uni `Everywhere` Eq, Eq fun, Eq ann) => Eq (Term NamedDeBruijn uni fun ann) +instance HashableTermConstraints uni fun ann => Hashable (Term NamedDeBruijn uni fun ann) + deriving stock instance (GEq uni, Closed uni, uni `Everywhere` Eq, Eq fun, Eq ann) => Eq (Term FakeNamedDeBruijn uni fun ann) +instance HashableTermConstraints uni fun ann => Hashable (Term FakeNamedDeBruijn uni fun ann) + deriving stock instance (GEq uni, Closed uni, uni `Everywhere` Eq, Eq fun, Eq ann) => Eq (Term DeBruijn uni fun ann) +instance HashableTermConstraints uni fun ann => Hashable (Term DeBruijn uni fun ann) + deriving stock instance (GEq uni, Closed uni, uni `Everywhere` Eq, Eq fun, Eq ann, Eq (Term name uni fun ann) ) => Eq (Program name uni fun ann)