1
1
{-# LANGUAGE DerivingVia #-}
2
2
{-# LANGUAGE FlexibleInstances #-}
3
+ {-# LANGUAGE InstanceSigs #-}
3
4
{-# LANGUAGE ScopedTypeVariables #-}
4
5
{-# LANGUAGE TypeApplications #-}
5
6
{-# LANGUAGE TypeFamilies #-}
6
7
7
8
module Cardano.Api.ScriptData (
8
9
-- * Script data
10
+ HashableScriptData ,
11
+ hashScriptDataBytes ,
12
+ getOriginalScriptDataBytes ,
13
+ getScriptData ,
9
14
ScriptData (.. ),
10
15
11
16
-- * Script data hashes
@@ -23,6 +28,9 @@ module Cardano.Api.ScriptData (
23
28
ScriptDataJsonSchemaError (.. ),
24
29
scriptDataFromJsonDetailedSchema ,
25
30
scriptDataToJsonDetailedSchema ,
31
+ ScriptBytesError (.. ),
32
+ ScriptDataJsonBytesError (.. ),
33
+ scriptDataJsonToHashable ,
26
34
27
35
-- * Internal conversion functions
28
36
toPlutusData ,
@@ -35,11 +43,15 @@ module Cardano.Api.ScriptData (
35
43
Hash (.. ),
36
44
) where
37
45
46
+ import qualified Cardano.Binary as CBOR
47
+ import Codec.Serialise.Class (Serialise (.. ))
38
48
import Data.Bifunctor (first )
49
+ import Data.ByteString (ByteString )
39
50
import qualified Data.ByteString as BS
40
51
import qualified Data.ByteString.Base16 as Base16
41
52
import qualified Data.ByteString.Char8 as BSC
42
53
import qualified Data.ByteString.Lazy.Char8 as LBS
54
+ import qualified Data.ByteString.Short as SB
43
55
import qualified Data.Char as Char
44
56
import Data.Either.Combinators
45
57
import qualified Data.List as List
@@ -77,14 +89,35 @@ import Cardano.Api.Keys.Shelley
77
89
import Cardano.Api.SerialiseCBOR
78
90
import Cardano.Api.SerialiseJSON
79
91
import Cardano.Api.SerialiseRaw
80
- import qualified Cardano.Binary as CBOR
81
-
82
92
import Cardano.Api.SerialiseUsing
83
93
import Cardano.Api.TxMetadata (pBytes , pSigned , parseAll )
84
- import Codec.Serialise.Class (Serialise (.. ))
94
+
95
+ -- Original script data bytes
96
+ data HashableScriptData
97
+ = HashableScriptData
98
+ ! BS. ByteString -- ^ Original 'ScriptData' bytes
99
+ ! ScriptData
100
+ deriving (Eq , Show )
101
+
102
+ instance HasTypeProxy HashableScriptData where
103
+ data AsType HashableScriptData = AsHashableScriptData
104
+ proxyToAsType _ = AsHashableScriptData
105
+
106
+ instance SerialiseAsCBOR HashableScriptData where
107
+ serialiseToCBOR (HashableScriptData _ sd) = CBOR. serialize' sd
108
+ deserialiseFromCBOR AsHashableScriptData bs =
109
+ HashableScriptData bs
110
+ <$> CBOR. decodeFullDecoder " ScriptData" fromCBOR (LBS. fromStrict bs)
111
+
112
+
113
+ getOriginalScriptDataBytes :: HashableScriptData -> BS. ByteString
114
+ getOriginalScriptDataBytes (HashableScriptData bs _) = bs
115
+
116
+ getScriptData :: HashableScriptData -> ScriptData
117
+ getScriptData (HashableScriptData _ sd) = sd
85
118
86
119
-- ----------------------------------------------------------------------------
87
- -- Script data
120
+ -- Script data - Only used for rendering
88
121
--
89
122
90
123
data ScriptData = ScriptDataConstructor
@@ -131,24 +164,41 @@ instance ToCBOR ScriptData where
131
164
toCBOR = encode @ Plutus. Data . toPlutusData
132
165
133
166
instance FromCBOR ScriptData where
167
+ fromCBOR :: CBOR. Decoder s ScriptData
134
168
fromCBOR = fromPlutusData <$> decode @ Plutus. Data
135
169
136
- hashScriptData :: ScriptData -> Hash ScriptData
137
- hashScriptData = ScriptDataHash
138
- . Alonzo. hashData
139
- . (toAlonzoData :: ScriptData -> Alonzo. Data StandardAlonzo )
170
+ hashScriptDataBytes :: HashableScriptData -> Hash ScriptData
171
+ hashScriptDataBytes =
172
+ (ScriptDataHash . Alonzo. hashData) . (toAlonzoData :: HashableScriptData -> Alonzo. Data StandardAlonzo )
140
173
174
+ {-# DEPRECATED hashScriptData "Use hashScriptDataBytes" #-}
175
+ hashScriptData :: HashableScriptData -> Hash ScriptData
176
+ hashScriptData = hashScriptDataBytes
141
177
142
178
-- ----------------------------------------------------------------------------
143
179
-- Conversion functions
144
180
--
145
181
146
- toAlonzoData :: ScriptData -> Alonzo. Data ledgerera
147
- toAlonzoData = Alonzo. Data . toPlutusData
182
+ newtype ScriptBytesError = ScriptBytesError String deriving Show
183
+
184
+ -- There is a subtlety here. We must use the original bytes
185
+ -- when converting to and from `HashableScriptData`/`Data`. This
186
+ -- avoids problems that arise due to reserialization of the script
187
+ -- data i.e differing script data hashes due to the re-encoding being slightly
188
+ -- different to the original encoding. See: https://github.com/input-output-hk/cardano-ledger/issues/2943
148
189
149
- fromAlonzoData :: Alonzo. Data ledgerera -> ScriptData
150
- fromAlonzoData = fromPlutusData . Alonzo. getPlutusData
190
+ toAlonzoData :: HashableScriptData -> Alonzo. Data ledgerera
191
+ toAlonzoData =
192
+ either
193
+ (\ e -> error $ " toAlonzoData: " <> show e)
194
+ Alonzo. binaryDataToData
195
+ . first ScriptBytesError . Alonzo. makeBinaryData . SB. toShort . getOriginalScriptDataBytes
151
196
197
+ fromAlonzoData :: Alonzo. Data ledgerera -> HashableScriptData
198
+ fromAlonzoData d =
199
+ HashableScriptData
200
+ (Ledger. originalBytes d)
201
+ (fromPlutusData $ Alonzo. getPlutusData d)
152
202
153
203
toPlutusData :: ScriptData -> Plutus. Data
154
204
toPlutusData (ScriptDataConstructor int xs)
@@ -327,10 +377,10 @@ data ScriptDataJsonSchema =
327
377
--
328
378
scriptDataFromJson :: ScriptDataJsonSchema
329
379
-> Aeson. Value
330
- -> Either ScriptDataJsonError ScriptData
380
+ -> Either ScriptDataJsonError HashableScriptData
331
381
scriptDataFromJson schema v = do
332
382
d <- first (ScriptDataJsonSchemaError v) (scriptDataFromJson' v)
333
- first (ScriptDataRangeError v) (validateScriptData d)
383
+ first (ScriptDataRangeError v) (validateScriptData $ getScriptData d)
334
384
return d
335
385
where
336
386
scriptDataFromJson' =
@@ -347,7 +397,7 @@ scriptDataFromJson schema v = do
347
397
-- See 'ScriptDataJsonSchema' for the details.
348
398
--
349
399
scriptDataToJson :: ScriptDataJsonSchema
350
- -> ScriptData
400
+ -> HashableScriptData
351
401
-> Aeson. Value
352
402
scriptDataToJson schema =
353
403
case schema of
@@ -359,8 +409,8 @@ scriptDataToJson schema =
359
409
-- JSON conversion using the the "no schema" style
360
410
--
361
411
362
- scriptDataToJsonNoSchema :: ScriptData -> Aeson. Value
363
- scriptDataToJsonNoSchema = conv
412
+ scriptDataToJsonNoSchema :: HashableScriptData -> Aeson. Value
413
+ scriptDataToJsonNoSchema = conv . getScriptData
364
414
where
365
415
conv :: ScriptData -> Aeson. Value
366
416
conv (ScriptDataNumber n) = Aeson. Number (fromInteger n)
@@ -400,8 +450,8 @@ scriptDataToJsonNoSchema = conv
400
450
401
451
scriptDataFromJsonNoSchema :: Aeson. Value
402
452
-> Either ScriptDataJsonSchemaError
403
- ScriptData
404
- scriptDataFromJsonNoSchema = conv
453
+ HashableScriptData
454
+ scriptDataFromJsonNoSchema = fmap ( \ sd -> HashableScriptData (serialiseToCBOR sd) sd) . conv
405
455
where
406
456
conv :: Aeson. Value
407
457
-> Either ScriptDataJsonSchemaError ScriptData
@@ -445,14 +495,37 @@ scriptDataFromJsonNoSchema = conv
445
495
-- be encoded as CBOR bytestrings.
446
496
bytesPrefix :: Text
447
497
bytesPrefix = " 0x"
448
-
498
+ data ScriptDataJsonBytesError
499
+ = ScriptDataJsonBytesErrorAesonDecode String
500
+ | ScriptDataJsonBytesErrorValue ScriptDataJsonError
501
+ | ScriptDataJsonBytesErrorInvalid ScriptDataRangeError
502
+ deriving Show
503
+ instance Error ScriptDataJsonBytesError where
504
+ displayError (ScriptDataJsonBytesErrorAesonDecode e) =
505
+ " Error decoding ScriptData JSON bytes: " <> e
506
+ displayError (ScriptDataJsonBytesErrorValue e) =
507
+ " Error decoding ScriptData JSON value: " <> show e
508
+ displayError (ScriptDataJsonBytesErrorInvalid e) =
509
+ " ScriptData is invalid: " <> show e
510
+
511
+ -- | This allows us to take JSON formatted ScriptData and encode it in the CDDL format
512
+ -- whilst preserving the original bytes.
513
+ scriptDataJsonToHashable
514
+ :: ScriptDataJsonSchema
515
+ -> ByteString -- ^ JSON encoded ScriptData
516
+ -> Either ScriptDataJsonBytesError HashableScriptData
517
+ scriptDataJsonToHashable schema jsonScriptDataBytes = do
518
+ scriptDataVal <- first ScriptDataJsonBytesErrorAesonDecode $ Aeson. eitherDecode' $ LBS. fromStrict jsonScriptDataBytes
519
+ sData <- first ScriptDataJsonBytesErrorValue $ scriptDataFromJson schema scriptDataVal
520
+ first ScriptDataJsonBytesErrorInvalid $ validateScriptData $ getScriptData sData
521
+ return sData
449
522
450
523
-- ----------------------------------------------------------------------------
451
524
-- JSON conversion using the "detailed schema" style
452
525
--
453
526
454
- scriptDataToJsonDetailedSchema :: ScriptData -> Aeson. Value
455
- scriptDataToJsonDetailedSchema = conv
527
+ scriptDataToJsonDetailedSchema :: HashableScriptData -> Aeson. Value
528
+ scriptDataToJsonDetailedSchema = conv . getScriptData
456
529
where
457
530
conv :: ScriptData -> Aeson. Value
458
531
conv (ScriptDataNumber n) = singleFieldObject " int"
@@ -481,8 +554,8 @@ scriptDataToJsonDetailedSchema = conv
481
554
482
555
scriptDataFromJsonDetailedSchema :: Aeson. Value
483
556
-> Either ScriptDataJsonSchemaError
484
- ScriptData
485
- scriptDataFromJsonDetailedSchema = conv
557
+ HashableScriptData
558
+ scriptDataFromJsonDetailedSchema = fmap ( \ sd -> HashableScriptData (serialiseToCBOR sd) sd) . conv
486
559
where
487
560
conv :: Aeson. Value
488
561
-> Either ScriptDataJsonSchemaError ScriptData
0 commit comments