Skip to content

Commit

Permalink
Add WithCBOR
Browse files Browse the repository at this point in the history
In many places it's important to retain the original bytes that the user
supplied so that the hash is what they expect. This is especially
important for data sent as CBOR, since there's multiple way to represent
identical arrays.
  • Loading branch information
Robert 'Probie' Offner committed Oct 31, 2022
1 parent 521b64a commit d3869a1
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 0 deletions.
1 change: 1 addition & 0 deletions cardano-api/cardano-api.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ library
-- Splitting up the big Typed module:
Cardano.Api.Address
Cardano.Api.Block
Cardano.Api.CBOR
Cardano.Api.Certificate
Cardano.Api.Convenience.Constraints
Cardano.Api.Convenience.Construction
Expand Down
72 changes: 72 additions & 0 deletions cardano-api/src/Cardano/Api/CBOR.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}

-- | Helpers for retaining user supplied CBOR
module Cardano.Api.CBOR
( AsType(..)
, WithCBOR
, getCBOR
, getCBORShort
, withoutCBOR
, withCBORViaRoundtrip
) where

import Prelude

import Data.ByteString (ByteString)
import Data.ByteString.Short (ShortByteString, toShort)

import Cardano.Ledger.Crypto (StandardCrypto)
import Cardano.Ledger.SafeHash (HashAnnotated, SafeToHash, originalBytes)

import Cardano.Api.HasTypeProxy
import Cardano.Api.SerialiseCBOR

-- | A value, paired with its representation. To generate something of
-- type 'WithCBOR', either call 'deserialiseFromCBOR' if you already have
-- CBOR, or call 'withCBORViaRoundtrip' if you need to pair a value with the
-- CBOR from serialising it.
data WithCBOR a = WithCBOR !ByteString !a
deriving (Eq, Show)

instance Ord a => Ord (WithCBOR a) where
compare (WithCBOR bx x) (WithCBOR by y) = case compare x y of
EQ -> compare bx by -- We need to match the derived Eq
res -> res

-- | Extract the CBOR from a 'WithCBOR'
getCBOR :: WithCBOR a -> ByteString
getCBOR (WithCBOR bs _ ) = bs

-- | Extract the CBOR as a short bytestring
getCBORShort :: WithCBOR a -> ShortByteString
getCBORShort = toShort . getCBOR

-- | Extract the value from a 'WithCBOR'
withoutCBOR :: WithCBOR a -> a
withoutCBOR (WithCBOR _ x) = x

instance HasTypeProxy a => HasTypeProxy (WithCBOR a) where
data AsType (WithCBOR a) = AsWithCBOR (AsType a)
proxyToAsType _ = AsWithCBOR (proxyToAsType (Proxy @a))

instance SerialiseAsCBOR a => SerialiseAsCBOR (WithCBOR a) where
serialiseToCBOR (WithCBOR bs _) = bs
deserialiseFromCBOR (AsWithCBOR p) bs = WithCBOR bs <$> deserialiseFromCBOR p bs

instance SafeToHash (WithCBOR a) where
originalBytes = getCBOR

instance HashAnnotated (WithCBOR a) a StandardCrypto

-- | Create a value of type 'WithCBOR' by first serialising it and then deserialising it.
-- Note that the value stored here may not be the original value of serialisation doesn't
-- roundtrip, and this will throw an error if we can't deserialise from the generated
-- CBOR.
withCBORViaRoundtrip :: forall a . SerialiseAsCBOR a => a -> WithCBOR a
withCBORViaRoundtrip x = case deserialiseFromCBOR (AsWithCBOR (proxyToAsType $ Proxy @a)) (serialiseToCBOR x) of
Left err -> error $ "withCBORViaRoundtrip: Does not round trip " <> show err
Right x' -> x'

0 comments on commit d3869a1

Please sign in to comment.