1
+ {-# LANGUAGE BinaryLiterals #-}
1
2
{-# LANGUAGE DataKinds #-}
2
3
{-# LANGUAGE DefaultSignatures #-}
3
4
{-# LANGUAGE FlexibleInstances #-}
5
+ {-# LANGUAGE InstanceSigs #-}
4
6
{-# LANGUAGE LambdaCase #-}
7
+ {-# LANGUAGE NumericUnderscores #-}
5
8
{-# LANGUAGE RankNTypes #-}
6
9
{-# LANGUAGE ScopedTypeVariables #-}
7
10
{-# LANGUAGE TypeApplications #-}
8
11
{-# LANGUAGE TypeFamilies #-}
12
+ {-# LANGUAGE TypeOperators #-}
9
13
10
14
module Cardano.Api.Serialise.Cip129
11
15
( Cip129 (.. )
16
+ , Cip129EncodingError
12
17
, deserialiseFromBech32Cip129
13
18
, serialiseToBech32Cip129
14
19
, serialiseGovActionIdToBech32Cip129
@@ -17,26 +22,29 @@ module Cardano.Api.Serialise.Cip129
17
22
)
18
23
where
19
24
25
+ import Cardano.Api.Error
20
26
import Cardano.Api.Governance.Internal.Action.ProposalProcedure
21
27
import Cardano.Api.HasTypeProxy
22
28
import Cardano.Api.Internal.Orphans (AsType (.. ))
23
29
import Cardano.Api.Monad.Error
30
+ import Cardano.Api.Pretty
24
31
import Cardano.Api.Serialise.Bech32
25
32
import Cardano.Api.Serialise.Raw
33
+ import Cardano.Api.Serialise.SerialiseUsing
26
34
35
+ import Cardano.Crypto.Hash.Class qualified as Hash
27
36
import Cardano.Ledger.Conway.Governance qualified as Gov
37
+ import Cardano.Ledger.Core qualified as L
28
38
import Cardano.Ledger.Credential (Credential (.. ))
29
39
import Cardano.Ledger.Credential qualified as L
30
- import Cardano.Ledger.Keys qualified as L
31
40
32
41
import Codec.Binary.Bech32 qualified as Bech32
33
42
import Control.Monad (guard )
34
- import Data.ByteString (ByteString )
35
43
import Data.ByteString qualified as BS
36
44
import Data.ByteString.Base16 qualified as Base16
37
- import Data.ByteString.Char8 qualified as C8
38
45
import Data.Text (Text )
39
- import Data.Text.Encoding qualified as Text
46
+ import Data.Typeable
47
+ import Data.Word (Word8 )
40
48
import GHC.Exts (IsList (.. ))
41
49
42
50
-- | Cip-129 is a typeclass that captures the serialisation requirements of https://cips.cardano.org/cip/CIP-0129
@@ -45,115 +53,174 @@ class (SerialiseAsRawBytes a, HasTypeProxy a) => Cip129 a where
45
53
-- | The human readable part of the Bech32 encoding for the credential.
46
54
cip129Bech32PrefixFor :: AsType a -> Bech32. HumanReadablePart
47
55
48
- -- | The header byte that identifies the credential type according to Cip-129.
49
- cip129HeaderHexByte :: a -> ByteString
50
-
51
- -- | Permitted bech32 prefixes according to Cip-129.
56
+ -- | Permitted bech32 prefixes according to CIP-129.
52
57
cip129Bech32PrefixesPermitted :: AsType a -> [Text ]
53
58
default cip129Bech32PrefixesPermitted :: AsType a -> [Text ]
54
59
cip129Bech32PrefixesPermitted = return . Bech32. humanReadablePartToText . cip129Bech32PrefixFor
55
60
61
+ -- | Serialise a value to a binary representation used in CIP 129. It's usually distinct from CBOR serialisation.
62
+ -- Internal conversion function. Use 'serialiseToBech32Cip129' instead of calling this function directly.
63
+ cip129SerialiseRaw :: a -> BS. ByteString
64
+
65
+ -- | Deserialise a value from the bytes representation. Internal conversion function. Use
66
+ -- 'deserialiseFromBech32Cip129' instead of calling this function directly.
67
+ cip129DeserialiseRaw :: BS. ByteString -> Either Cip129EncodingError a
68
+
69
+ -- | CIP-129 decoding errors
70
+ data Cip129EncodingError
71
+ = CeeTypeDecodingError TypeRep BS. ByteString
72
+ | CeeUnknownHeaderError TypeRep Word8
73
+ | CeeEmptyBytesError TypeRep
74
+ | CeeBech32Error TypeRep Bech32DecodeError
75
+ deriving (Eq , Show )
76
+
77
+ instance Error Cip129EncodingError where
78
+ prettyError = \ case
79
+ CeeTypeDecodingError tr bytes ->
80
+ " Cannot decode CIP129 encoding of a type \" "
81
+ <> pretty tr
82
+ <> " \" , bytes hex: "
83
+ <> pretty (UsingRawBytesHex bytes)
84
+ CeeUnknownHeaderError tr header ->
85
+ " Cannot decode CIP129 header of a type \" "
86
+ <> pretty tr
87
+ <> " \" , header bytes hex: "
88
+ <> pretty (UsingRawBytesHex header)
89
+ CeeEmptyBytesError tr ->
90
+ " Cannot decode CIP129 header of a type \" " <> pretty tr <> " \" , cannot decode empty bytes"
91
+ CeeBech32Error tr be ->
92
+ " Cannot decode CIP129 encoding of a type \" "
93
+ <> pretty tr
94
+ <> " \" , due to Bech32 decoding error: "
95
+ <> prettyError be
96
+
56
97
instance Cip129 (Credential L. ColdCommitteeRole ) where
57
98
cip129Bech32PrefixFor _ = unsafeHumanReadablePartFromText " cc_cold"
58
99
cip129Bech32PrefixesPermitted AsColdCommitteeCredential = [" cc_cold" ]
59
100
60
- cip129HeaderHexByte =
61
- BS. singleton . \ case
62
- L. KeyHashObj {} -> 0x12 -- 0001 0010
63
- L. ScriptHashObj {} -> 0x13 -- 0001 0011
101
+ cip129SerialiseRaw = \ case
102
+ L. KeyHashObj (L. KeyHash kh) -> BS. singleton 0b0001_0010 <> Hash. hashToBytes kh
103
+ L. ScriptHashObj (L. ScriptHash sh) -> BS. singleton 0b0001_0011 <> Hash. hashToBytes sh
104
+
105
+ cip129DeserialiseRaw
106
+ :: forall a
107
+ . a ~ Credential L. ColdCommitteeRole
108
+ => BS. ByteString
109
+ -> Either Cip129EncodingError a
110
+ cip129DeserialiseRaw bytes = do
111
+ let t = typeRep $ Proxy @ a
112
+ case BS. uncons bytes of
113
+ Just (0b0001_0010 , cred) -> L. KeyHashObj . L. KeyHash <$> Hash. hashFromBytes cred ?! CeeTypeDecodingError t bytes
114
+ Just (0b0001_0011 , cred) -> L. ScriptHashObj . L. ScriptHash <$> Hash. hashFromBytes cred ?! CeeTypeDecodingError t bytes
115
+ Just (header, _) -> throwError $ CeeUnknownHeaderError t header
116
+ Nothing -> throwError $ CeeEmptyBytesError t
64
117
65
118
instance Cip129 (Credential L. HotCommitteeRole ) where
66
119
cip129Bech32PrefixFor _ = unsafeHumanReadablePartFromText " cc_hot"
67
120
cip129Bech32PrefixesPermitted AsHotCommitteeCredential = [" cc_hot" ]
68
- cip129HeaderHexByte =
69
- BS. singleton . \ case
70
- L. KeyHashObj {} -> 0x02 -- 0000 0010
71
- L. ScriptHashObj {} -> 0x03 -- 0000 0011
121
+
122
+ cip129SerialiseRaw = \ case
123
+ L. KeyHashObj (L. KeyHash kh) -> BS. singleton 0b0000_0010 <> Hash. hashToBytes kh
124
+ L. ScriptHashObj (L. ScriptHash sh) -> BS. singleton 0b0000_0011 <> Hash. hashToBytes sh
125
+
126
+ cip129DeserialiseRaw
127
+ :: forall a
128
+ . a ~ Credential L. HotCommitteeRole
129
+ => BS. ByteString
130
+ -> Either Cip129EncodingError a
131
+ cip129DeserialiseRaw bytes = do
132
+ let t = typeRep $ Proxy @ a
133
+ case BS. uncons bytes of
134
+ Just (0b0000_0010 , cred) -> L. KeyHashObj . L. KeyHash <$> Hash. hashFromBytes cred ?! CeeTypeDecodingError t bytes
135
+ Just (0b0000_0011 , cred) -> L. ScriptHashObj . L. ScriptHash <$> Hash. hashFromBytes cred ?! CeeTypeDecodingError t bytes
136
+ Just (header, _) -> throwError $ CeeUnknownHeaderError t header
137
+ Nothing -> throwError $ CeeEmptyBytesError t
72
138
73
139
instance Cip129 (Credential L. DRepRole ) where
74
140
cip129Bech32PrefixFor _ = unsafeHumanReadablePartFromText " drep"
75
141
cip129Bech32PrefixesPermitted AsDrepCredential = [" drep" ]
76
- cip129HeaderHexByte =
77
- BS. singleton . \ case
78
- L. KeyHashObj {} -> 0x22 -- 0010 0010
79
- L. ScriptHashObj {} -> 0x23 -- 0010 0011
80
-
81
- -- | Serialize a accoding to the serialisation requirements of https://cips.cardano.org/cip/CIP-0129
82
- -- which currently pertain to governance credentials. Governance action ids are dealt separately with
83
- -- via 'serialiseGovActionIdToBech32Cip129'.
142
+
143
+ cip129SerialiseRaw = \ case
144
+ L. KeyHashObj (L. KeyHash kh) -> BS. singleton 0b0010_0010 <> Hash. hashToBytes kh
145
+ L. ScriptHashObj (L. ScriptHash sh) -> BS. singleton 0b0010_0011 <> Hash. hashToBytes sh
146
+
147
+ cip129DeserialiseRaw
148
+ :: forall a
149
+ . a ~ Credential L. DRepRole
150
+ => BS. ByteString
151
+ -> Either Cip129EncodingError a
152
+ cip129DeserialiseRaw bytes = do
153
+ let t = typeRep $ Proxy @ a
154
+ case BS. uncons bytes of
155
+ Just (0b0010_0010 , cred) -> L. KeyHashObj . L. KeyHash <$> Hash. hashFromBytes cred ?! CeeTypeDecodingError t bytes
156
+ Just (0b0010_0011 , cred) -> L. ScriptHashObj . L. ScriptHash <$> Hash. hashFromBytes cred ?! CeeTypeDecodingError t bytes
157
+ Just (header, _) -> throwError $ CeeUnknownHeaderError t header
158
+ Nothing -> throwError $ CeeEmptyBytesError t
159
+
160
+ instance Cip129 Gov. GovActionId where
161
+ cip129Bech32PrefixFor _ = unsafeHumanReadablePartFromText " gov_action"
162
+ cip129Bech32PrefixesPermitted AsGovActionId = [" gov_action" ]
163
+
164
+ cip129SerialiseRaw = serialiseToRawBytes
165
+
166
+ cip129DeserialiseRaw
167
+ :: forall a
168
+ . a ~ Gov. GovActionId
169
+ => BS. ByteString
170
+ -> Either Cip129EncodingError a
171
+ cip129DeserialiseRaw bs =
172
+ deserialiseFromRawBytes AsGovActionId bs ?!& const (CeeTypeDecodingError (typeRep $ Proxy @ a ) bs)
173
+
174
+ -- | Serialise a accoding to the serialisation requirements of https://cips.cardano.org/cip/CIP-0129
175
+ -- which currently pertain to governance credentials.
84
176
serialiseToBech32Cip129 :: forall a . Cip129 a => a -> Text
85
177
serialiseToBech32Cip129 a =
86
178
Bech32. encodeLenient
87
179
humanReadablePart
88
- (Bech32. dataPartFromBytes (cip129HeaderHexByte a <> serialiseToRawBytes a) )
180
+ (Bech32. dataPartFromBytes $ cip129SerialiseRaw a )
89
181
where
90
- humanReadablePart = cip129Bech32PrefixFor (proxyToAsType ( Proxy :: Proxy a ) )
182
+ humanReadablePart = cip129Bech32PrefixFor (asType @ a )
91
183
184
+ -- | Deserialise a governance identifier from CIP-129 format.
92
185
deserialiseFromBech32Cip129
93
186
:: forall a
94
187
. Cip129 a
95
188
=> Text
96
- -> Either Bech32DecodeError a
189
+ -- ^ A Bech32-encoded governance identifier
190
+ -> Either Cip129EncodingError a
97
191
deserialiseFromBech32Cip129 bech32Str = do
192
+ let type' = typeRep $ Proxy @ a
98
193
(prefix, dataPart) <-
99
194
Bech32. decodeLenient bech32Str
100
- ?!& Bech32DecodingError
195
+ ?!& CeeBech32Error type'
196
+ . Bech32DecodingError
101
197
102
198
let actualPrefix = Bech32. humanReadablePartToText prefix
103
199
permittedPrefixes = cip129Bech32PrefixesPermitted (asType @ a )
104
200
guard (actualPrefix `elem` permittedPrefixes)
105
- ?! Bech32UnexpectedPrefix actualPrefix (fromList permittedPrefixes)
201
+ ?! CeeBech32Error type' ( Bech32UnexpectedPrefix actualPrefix (fromList permittedPrefixes) )
106
202
107
203
payload <-
108
204
Bech32. dataPartToBytes dataPart
109
- ?! Bech32DataPartToBytesError (Bech32. dataPartToText dataPart)
110
-
111
- (header, credential) <-
112
- case C8. uncons payload of
113
- Just (header, credential) -> return (C8. singleton header, credential)
114
- Nothing -> Left $ Bech32DeserialiseFromBytesError payload
205
+ ?! CeeBech32Error type' (Bech32DataPartToBytesError (Bech32. dataPartToText dataPart))
115
206
116
- value <- case deserialiseFromRawBytes asType credential of
117
- Right a -> Right a
118
- Left _ -> Left $ Bech32DeserialiseFromBytesError payload
119
-
120
- let expectedHeader = cip129HeaderHexByte value
121
-
122
- guard (header == expectedHeader)
123
- ?! Bech32UnexpectedHeader (toBase16Text expectedHeader) (toBase16Text header)
207
+ value <-
208
+ cip129DeserialiseRaw payload
209
+ ?!& const (CeeBech32Error type' . Bech32DeserialiseFromBytesError $ Base16. encode payload)
124
210
125
211
let expectedPrefix = Bech32. humanReadablePartToText $ cip129Bech32PrefixFor (asType @ a )
126
212
guard (actualPrefix == expectedPrefix)
127
- ?! Bech32WrongPrefix actualPrefix expectedPrefix
213
+ ?! CeeBech32Error type' ( Bech32WrongPrefix actualPrefix expectedPrefix)
128
214
129
- return value
130
- where
131
- toBase16Text = Text. decodeUtf8 . Base16. encode
215
+ pure value
132
216
133
217
-- | Governance Action ID
134
218
-- According to Cip129 there is no header byte for GovActionId.
135
219
-- Instead they append the txid and index to form the payload.
220
+ {-# DEPRECATED serialiseGovActionIdToBech32Cip129 "Use serialiseToBech32Cip129 instead" #-}
136
221
serialiseGovActionIdToBech32Cip129 :: Gov. GovActionId -> Text
137
- serialiseGovActionIdToBech32Cip129 govActionId = do
138
- let humanReadablePart = unsafeHumanReadablePartFromText " gov_action"
139
- Bech32. encodeLenient
140
- humanReadablePart
141
- (Bech32. dataPartFromBytes $ serialiseToRawBytes govActionId)
142
-
143
- deserialiseGovActionIdFromBech32Cip129
144
- :: Text -> Either Bech32DecodeError Gov. GovActionId
145
- deserialiseGovActionIdFromBech32Cip129 bech32Str = do
146
- let permittedPrefixes = [" gov_action" ]
147
- (prefix, dataPart) <-
148
- Bech32. decodeLenient bech32Str
149
- ?!& Bech32DecodingError
150
- let actualPrefix = Bech32. humanReadablePartToText prefix
151
- guard (actualPrefix `elem` permittedPrefixes)
152
- ?! Bech32UnexpectedPrefix actualPrefix (fromList permittedPrefixes)
153
-
154
- payload <-
155
- Bech32. dataPartToBytes dataPart
156
- ?! Bech32DataPartToBytesError (Bech32. dataPartToText dataPart)
222
+ serialiseGovActionIdToBech32Cip129 = serialiseToBech32Cip129
157
223
158
- deserialiseFromRawBytes AsGovActionId payload
159
- ?!& const (Bech32DeserialiseFromBytesError payload)
224
+ {-# DEPRECATED deserialiseGovActionIdFromBech32Cip129 "Use deserialiseFromBech32Cip129 instead" #-}
225
+ deserialiseGovActionIdFromBech32Cip129 :: Text -> Either Cip129EncodingError Gov. GovActionId
226
+ deserialiseGovActionIdFromBech32Cip129 = deserialiseFromBech32Cip129
0 commit comments