Skip to content

Commit d9fa5c7

Browse files
authored
Merge pull request #720 from IntersectMBO/jordan/plutus-script-in-era
Fix plutus double CBOR encoding bug
2 parents 21e9ab8 + 80760ee commit d9fa5c7

7 files changed

Lines changed: 382 additions & 36 deletions

File tree

cardano-api/cardano-api.cabal

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,7 @@ library gen
278278
Test.Gen.Cardano.Api
279279
Test.Gen.Cardano.Api.Byron
280280
Test.Gen.Cardano.Api.Era
281+
Test.Gen.Cardano.Api.Hardcoded
281282
Test.Gen.Cardano.Api.Metadata
282283
Test.Gen.Cardano.Api.ProtocolParameters
283284
Test.Gen.Cardano.Api.Typed
@@ -320,6 +321,7 @@ test-suite cardano-api-test
320321
build-depends:
321322
QuickCheck,
322323
aeson >=1.5.6.0,
324+
base16-bytestring,
323325
bytestring,
324326
cardano-api,
325327
cardano-api:gen,
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
module Test.Gen.Cardano.Api.Hardcoded
2+
( exampleDoubleEncodedBytes
3+
, exampleDoubleEncodedBytesEncoding
4+
, v1Loop2024PlutusScriptHex
5+
, v1Loop2024PlutusScriptHexDoubleEncoded
6+
, v2EcdsaLoopPlutusScriptHex
7+
, v2EcdsaLoopPlutusScriptHexDoubleEncoded
8+
, v3AlwaysSucceedsPlutusScript
9+
, v3AlwaysSucceedsPlutusScriptDoubleEncoded
10+
)
11+
where
12+
13+
import qualified Cardano.Binary as CBOR
14+
15+
import Data.ByteString (ByteString)
16+
import qualified Data.ByteString as BS
17+
import qualified Data.ByteString.Lazy as LBS
18+
19+
exampleDoubleEncodedBytes :: LBS.ByteString
20+
exampleDoubleEncodedBytes = LBS.fromStrict $ CBOR.toStrictByteString exampleDoubleEncodedBytesEncoding
21+
22+
exampleDoubleEncodedBytesEncoding :: CBOR.Encoding
23+
exampleDoubleEncodedBytesEncoding = do
24+
CBOR.encodeBytes $
25+
CBOR.toStrictByteString $
26+
CBOR.encodeBytes "testBytes"
27+
28+
v1Loop2024PlutusScriptHexDoubleEncoded :: ByteString
29+
v1Loop2024PlutusScriptHexDoubleEncoded =
30+
"5850584e010000332232222325335333573466e200052080897a0070061613005001375a00464600200244a66a666ae68cdc3a410112f40020080062240022646600600600266e0400520021220021220011"
31+
32+
v1Loop2024PlutusScriptHex :: ByteString
33+
v1Loop2024PlutusScriptHex = BS.drop 4 v1Loop2024PlutusScriptHexDoubleEncoded
34+
35+
v2EcdsaLoopPlutusScriptHexDoubleEncoded :: ByteString
36+
v2EcdsaLoopPlutusScriptHexDoubleEncoded =
37+
"59023f59023c01000033223232322225335332233333233001005225335333573466e1d200000200d00c153323533335573e0044a00c4600e660046ae8400cd5d1001806109a80091299a9999998038011128051280492804918050009280490a99a9999aab9f0022500a2300b33006357420066ae8800c04084d4004894cd4cccccc02c0088940389403494034940348c038004854cd4cccd55cf8011280711807998051aba100335744006028426a00244a66a66666601e00444a0244a0224a0224a0224602400242a66a6666aae7c008940488c8c8c054008d5d10021aba10030182153353333330110012250142501325013250132301400121301412333300100c0080040021501215011150101500d1500c150091500822123300100300215004150042222223333333574800c4646600e6aae74004d55cf0009baa00723005375600e460086eb001c8c00cdd6803918011bae00700e25002250022500225002212230020031122001213500122225335333573466e200112080897a00d00c1300a4911572656465656d6572206973203c20313030303030300013333009004003002001130054911d5472616365206572726f723a20496e76616c69642072656465656d657200323001001222225335333573466e1d2080897a0040090081007153353335734666ed000c0080040240204cccc8cc018018004cdc0802240040060040022600c921245472616365206572726f723a2045434453412076616c69646174696f6e206661696c6564002326335738002004240022440042440021"
38+
39+
v2EcdsaLoopPlutusScriptHex :: ByteString
40+
v2EcdsaLoopPlutusScriptHex = BS.drop 6 v2EcdsaLoopPlutusScriptHexDoubleEncoded
41+
42+
v3AlwaysSucceedsPlutusScriptDoubleEncoded :: ByteString
43+
v3AlwaysSucceedsPlutusScriptDoubleEncoded =
44+
"590b2c590b29010100323232323232323232232498c8c8c954ccd5cd19b874800000844c8c8c8c8c8c8c8ca002646464aa666ae68cdc3a4000004226464646464646464646464646464646466666666666646664664664444444444444445001010807c03a01b00c805c02a013008803c01a00b004801c00a00230013574202860026ae8404cc0908c8c8c954ccd5cd19b87480000084600260406ae84006600a6ae84d5d1000844c0b52401035054310035573c0046aae74004dd5000998120009aba1011232323255333573466e1d20000021132328009919192a999ab9a3370e900000108c004c08cd5d0800ccc0848c8c8c954ccd5cd19b874800000846002604e6ae8400422aa666ae68cdc3a40040042265003375a6ae8400a6eb4d5d0800cdd69aba1357440023574400222606a9201035054310035573c0046aae74004dd50009aba135744002113031491035054310035573c0046aae74004dd51aba100398039aba10029919192a999ab9a3370e900000108c0004554ccd5cd19b87480080084600a6eb8d5d080084554ccd5cd19b8748010008460066ae840042260629201035054310035573c0046aae74004dd51aba10019980f3ae357426ae880046ae88004d5d1000889816249035054310035573c0046aae74004dd50009bad3574201e60026ae84038c004c005d69981100b1aba100c33301501975a6ae8402cc8c8c954ccd5cd19b874800000846002646464aa666ae68cdc3a4000004230013302b75a6ae8400660546ae84d5d1000844c0b5241035054310035573c0046aae74004dd51aba10019919192a999ab9a3370e900000108c004cc0add69aba100198151aba13574400211302d4901035054310035573c0046aae74004dd51aba13574400211302a4901035054310035573c0046aae74004dd51aba100a3302275c6ae84024ccc0548c8c8c954ccd5cd19b8748000008460066eb8d5d080084554ccd5cd19b874800800846012603c6ae8400422aa666ae68cdc3a400800423007301d357420021155333573466e1d2006002118009bad35742003301a357426ae8800422aa666ae68cdc3a40100042300b301c357420021155333573466e1d200a002118029bad357420033018357426ae880042260569201035054310035573c0046aae74004dd50008119aba1008330010233574200e6eb8d5d080319980a80c1980a81311919192a999ab9a3370e900000108c0084554ccd5cd19b87480080084600822aa666ae68cdc3a40080042300011302b491035054310035573c0046aae74004dd50009aba1005330220143574200860046ae8400cc008d5d09aba2003301475c602aeb4d5d10009aba2001357440026ae88004d5d10009aba2001357440026ae88004d5d10009aba2001357440026ae88004d5d10009aba20011130174901035054310035573c0046aae74004dd51aba10099aba10089919192a999ab9a3370e900000108c00cdd71aba100108aa999ab9a3370e900100108c024c028d5d0800ccc01c04cd5d09aba200108aa999ab9a3370e900200108c01cc024d5d080084554ccd5cd19b8748018008460026eb4d5d0800cc018d5d09aba200108aa999ab9a3370e900400108c02cc020d5d080084554ccd5cd19b87480280084600a6eb4d5d0800cc010d5d09aba200108980ba481035054310035573c0046aae74004dd51aba135744010232323255333573466e1d200000211328009bad35742005300a3574200332323255333573466e1d200000211328049980600d9aba10029aba1001998063ae357426ae880046ae880044554ccd5cd19b874800800846002660160346ae84006646464aa666ae68cdc3a400000423001375a6ae840066eb4d5d09aba200108980f2481035054310035573c0046aae74004dd51aba1357440021155333573466e1d200400211805999804806bad357420033300b75c6ae84d5d100084554ccd5cd19b87480180084600e660160346ae8400422aa666ae68cdc3a401000422646500d3300d01c357420073301800f3574200533300b00f75a6ae840072646464aa666ae68cdc3a400000423001375a6ae840066eb4d5d09aba20010898102481035054310035573c0046aae74004dd51aba13574400322330180020010d5d10009aba20011155333573466e1d200a002118029980580d1aba10019919192a999ab9a3370e900000108998073ae3574200222603c9201035054310035573c0046aae74004dd51aba1357440021155333573466e1d200c0021180108980da481035054310035573c0046aae74004dd51aba1357440023574400222602e9201035054310035573c0046aae74004dd50009119118011bab00130152233335573e0025000232801c004c018d55ce800cc014d55cf000a60086ae8800c6ae8400a0004646464aa666ae68cdc3a40000042300d3007357420033300575a6ae84d5d100084554ccd5cd19b874800800846026600e6ae840066600aeb4d5d09aba200108a992999ab9a3370e900200188c00cc020d5d08014c004d5d09aba200208aa999ab9a3370e90030018899402cc024d5d0801cc008d5d0800cdd69aba1357440023574400422aa666ae68cdc3a401000623009300835742005375a6ae84d5d100104554ccd5cd19b874802800c4602a60106ae8400822aa666ae68cdc3a401800623011300835742005375a6ae84d5d100104554ccd5cd19b874803800c4600a6eb8d5d08014dd71aba1357440041155333573466e1d2010003118039bae35742005375a6ae84d5d100104554ccd5cd19b874804800c4600260106ae8400a60106ae84d5d100104554ccd5cd19b874805000c4601e60106ae8400822602c9210350543100232323255333573466e1d2000002118009bae35742002115325333573466e1d20020031180298009aba100208aa999ab9a3370e900200188c00cdd71aba100298009aba13574400411301a49010350543100232323255333573466e1d20000021180098079aba100108aa999ab9a3370e900100108c0084554ccd5cd19b87480100084600822603a9201035054310035573c0046aae74004dd50009aab9e00235573a0026ea8004d55cf0011aab9d001375400244646464aa666ae68cdc3a4004004230021155333573466e1d20000021180098029aba100108980aa49035054310035573c0046aae74004dd500091919192a999ab9a3370e900000108c004c014d5d080084554ccd5cd19b874800800846006600a6ae8400422aa666ae68cdc3a400800423005375c6ae840042260269201035054310035573c0046aae74004dd500091919192a999ab9a3370e900000108c004dd71aba100108aa999ab9a3370e900100108c00cdd71aba1001089809249035054310035573c0046aae74004dd500091919192a999ab9a3370e900000108c004dd71aba10019bad357426ae880042260229201035054310035573c0046aae74004dd50009aba200111300c4901035054310035573c0046aae74004dd500098041112a999ab9a3370e9000000889805248103505433001155333573466e200052000113300333702900000119b814800000444ca00266e1000c00666e1000800466008004002600e444aa666ae68cdc3a400000222004226600600266e180080048c88c008dd60009803911999aab9f00128001400cc010d5d08014c00cd5d1001200040024646464aa666ae68cdc3a4000004230021155333573466e1d200200211800089803a481035054310035573c0046aae74004dd5000911919192a999ab9a3370e900000108c0084554ccd5cd19b874800800846002600a6ae8400422aa666ae68cdc3a400800423004113007491035054310035573c0046aae74004dd500091919192a999ab9a3370e900000108c004dd71aba10019bad357426ae8800422600a9201035054310035573c0046aae74004dd5000919319ab9c0018001191800800918011198010010009"
45+
46+
v3AlwaysSucceedsPlutusScript :: ByteString
47+
v3AlwaysSucceedsPlutusScript = BS.drop 6 v3AlwaysSucceedsPlutusScriptDoubleEncoded

cardano-api/gen/Test/Gen/Cardano/Api/Typed.hs

Lines changed: 80 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,12 @@ module Test.Gen.Cardano.Api.Typed
4646
, genHashableScriptData
4747
, genReferenceScript
4848
, genScript
49+
, genValidScript
4950
, genSimpleScript
5051
, genPlutusScript
52+
, genPlutusV1Script
53+
, genPlutusV2Script
54+
, genPlutusV3Script
5155
, genScriptInAnyLang
5256
, genScriptInEra
5357
, genScriptHash
@@ -159,8 +163,10 @@ import qualified Data.ByteString.Short as SBS
159163
import Data.Coerce
160164
import Data.Int (Int64)
161165
import Data.Maybe
166+
import qualified Data.ByteString.Base16 as Base16
162167
import Data.Ratio (Ratio, (%))
163168
import Data.String
169+
import Test.Gen.Cardano.Api.Hardcoded
164170
import Data.Word (Word16, Word32, Word64)
165171
import GHC.Exts (IsList (..))
166172
import GHC.Stack
@@ -211,6 +217,14 @@ genPositiveLovelace = L.Coin <$> Gen.integral (Range.linear 1 5000)
211217
-- SimpleScript generators
212218
--
213219

220+
-- This generator does not generate the deprecated double encoded plutus scripts
221+
genValidScript :: ScriptLanguage lang -> Gen (Script lang)
222+
genValidScript SimpleScriptLanguage =
223+
SimpleScript <$> genSimpleScript
224+
genValidScript (PlutusScriptLanguage lang) =
225+
PlutusScript lang <$> genValidPlutusScript lang
226+
227+
-- This generator will also generate the deprecated double encoded plutus scripts
214228
genScript :: ScriptLanguage lang -> Gen (Script lang)
215229
genScript SimpleScriptLanguage =
216230
SimpleScript <$> genSimpleScript
@@ -240,10 +254,72 @@ genSimpleScript =
240254
return (RequireMOf m ts)
241255
]
242256

257+
-- | 'genPlutusScript' will generate the deprecated double encoded
258+
-- plutus scripts as well as valid plutus scripts.
243259
genPlutusScript :: PlutusScriptVersion lang -> Gen (PlutusScript lang)
244-
genPlutusScript _ =
245-
-- We make no attempt to create a valid script
246-
PlutusScriptSerialised . SBS.toShort <$> Gen.bytes (Range.linear 0 32)
260+
genPlutusScript l =
261+
case l of
262+
PlutusScriptV1 -> do
263+
PlutusScript _ s <- genPlutusV1Script
264+
return s
265+
PlutusScriptV2 -> do
266+
PlutusScript _ s <- genPlutusV2Script
267+
return s
268+
PlutusScriptV3 -> do
269+
PlutusScript _ s <- genPlutusV3Script
270+
return s
271+
272+
genValidPlutusScript :: PlutusScriptVersion lang -> Gen (PlutusScript lang)
273+
genValidPlutusScript l =
274+
case l of
275+
PlutusScriptV1 -> do
276+
PlutusScript _ s <- genValidPlutusV1Script
277+
return s
278+
PlutusScriptV2 -> do
279+
PlutusScript _ s <- genValidPlutusV2Script
280+
return s
281+
PlutusScriptV3 -> do
282+
PlutusScript _ s <- genValidPlutusV3Script
283+
return s
284+
285+
genPlutusV1Script :: Gen (Script PlutusScriptV1)
286+
genPlutusV1Script = do
287+
v1Script <- Gen.element [v1Loop2024PlutusScriptHexDoubleEncoded,v1Loop2024PlutusScriptHex]
288+
let v1ScriptBytes = Base16.decodeLenient v1Script
289+
return . PlutusScript PlutusScriptV1 . PlutusScriptSerialised $ SBS.toShort v1ScriptBytes
290+
291+
genValidPlutusV1Script :: Gen (Script PlutusScriptV1)
292+
genValidPlutusV1Script = do
293+
v1Script <- Gen.element [v1Loop2024PlutusScriptHex]
294+
let v1ScriptBytes = Base16.decodeLenient v1Script
295+
return . PlutusScript PlutusScriptV1 . PlutusScriptSerialised $ SBS.toShort v1ScriptBytes
296+
297+
genPlutusV2Script :: Gen (Script PlutusScriptV2)
298+
genPlutusV2Script = do
299+
v2Script <- Gen.element [v2EcdsaLoopPlutusScriptHexDoubleEncoded, v2EcdsaLoopPlutusScriptHex]
300+
let v2ScriptBytes = Base16.decodeLenient v2Script
301+
return . PlutusScript PlutusScriptV2 . PlutusScriptSerialised $ SBS.toShort v2ScriptBytes
302+
303+
genValidPlutusV2Script :: Gen (Script PlutusScriptV2)
304+
genValidPlutusV2Script = do
305+
v2Script <- Gen.element [v2EcdsaLoopPlutusScriptHex]
306+
let v2ScriptBytes = Base16.decodeLenient v2Script
307+
return . PlutusScript PlutusScriptV2 . PlutusScriptSerialised $ SBS.toShort v2ScriptBytes
308+
309+
genPlutusV3Script :: Gen (Script PlutusScriptV3)
310+
genPlutusV3Script = do
311+
v3AlwaysSucceedsPlutusScriptHex
312+
<- Gen.element [v3AlwaysSucceedsPlutusScriptDoubleEncoded, v3AlwaysSucceedsPlutusScript]
313+
let v3ScriptBytes = Base16.decodeLenient v3AlwaysSucceedsPlutusScriptHex
314+
return . PlutusScript PlutusScriptV3 . PlutusScriptSerialised $ SBS.toShort v3ScriptBytes
315+
316+
genValidPlutusV3Script :: Gen (Script PlutusScriptV3)
317+
genValidPlutusV3Script = do
318+
v3AlwaysSucceedsPlutusScriptHex
319+
<- Gen.element [v3AlwaysSucceedsPlutusScript]
320+
let v3ScriptBytes = Base16.decodeLenient v3AlwaysSucceedsPlutusScriptHex
321+
return . PlutusScript PlutusScriptV3 . PlutusScriptSerialised $ SBS.toShort v3ScriptBytes
322+
247323

248324
genScriptDataSchema :: Gen ScriptDataJsonSchema
249325
genScriptDataSchema = Gen.element [ScriptDataJsonNoSchema, ScriptDataJsonDetailedSchema]
@@ -320,7 +396,7 @@ genScriptInAnyLang =
320396
genScriptInEra :: ShelleyBasedEra era -> Gen (ScriptInEra era)
321397
genScriptInEra era =
322398
Gen.choice
323-
[ ScriptInEra langInEra <$> genScript lang
399+
[ ScriptInEra langInEra <$> genValidScript lang
324400
| AnyScriptLanguage lang <- [minBound .. maxBound]
325401
, Just langInEra <- [scriptLanguageSupportedInEra era lang]
326402
]

cardano-api/gen/Test/Hedgehog/Roundtrip/CBOR.hs

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,30 @@
11
{-# LANGUAGE FlexibleContexts #-}
2+
{-# LANGUAGE GADTs #-}
23
{-# LANGUAGE RankNTypes #-}
34
{-# LANGUAGE ScopedTypeVariables #-}
5+
{-# LANGUAGE TypeApplications #-}
46

57
module Test.Hedgehog.Roundtrip.CBOR
6-
( trippingCbor
8+
( decodeOnlyPlutusScriptBytes
9+
, trippingCbor
710
)
811
where
912

1013
import Cardano.Api
14+
import Cardano.Api.Eon.ShelleyBasedEra
15+
import Cardano.Api.Script
1116

17+
import qualified Cardano.Ledger.Core as Ledger
18+
import qualified Cardano.Ledger.Plutus.Language as Plutus
19+
20+
import Data.ByteString (ByteString)
21+
import qualified Data.ByteString.Short as SBS
22+
import Data.Proxy
1223
import GHC.Stack (HasCallStack)
1324
import qualified GHC.Stack as GHC
1425

1526
import qualified Hedgehog as H
27+
import Hedgehog.Internal.Property (failWith)
1628

1729
{- HLINT ignore "Use camelCase" -}
1830

@@ -30,3 +42,59 @@ trippingCbor
3042
trippingCbor typeProxy v =
3143
GHC.withFrozenCallStack $
3244
H.tripping v serialiseToCBOR (deserialiseFromCBOR typeProxy)
45+
46+
-- | We need to confirm the existing 'SerialiseAsCBOR' instance for 'Script lang'
47+
-- no longer double serializes scripts but is backwards compatible with
48+
-- doubly serialized scripts.
49+
--
50+
-- We would also like to check that the deserialized bytes is a valid
51+
-- plutus script. We can do this by using the 'SerialiseAsCBOR' instance for
52+
-- 'PlutusScriptInEra'.
53+
--
54+
-- We will check the following:
55+
-- 1. Deserializing double encoded script bytes and "normal" script bytes
56+
-- decode to the same byte sequence.
57+
-- 2. The resulting bytes are both valid plutus scripts (via 'PlutusScriptInEra')
58+
--
59+
-- If these two properties hold we can be sure that existing double encoded scripts
60+
-- will deserialize correctly and newly created scripts will also deserialize correctly.
61+
decodeOnlyPlutusScriptBytes
62+
:: forall era lang m
63+
. HasCallStack
64+
=> Ledger.Era (ShelleyLedgerEra era)
65+
=> H.MonadTest m
66+
=> Plutus.PlutusLanguage (ToLedgerPlutusLanguage lang)
67+
=> IsPlutusScriptLanguage lang
68+
=> HasTypeProxy era
69+
=> ShelleyBasedEra era
70+
-> PlutusScriptVersion lang
71+
-> ByteString
72+
-- ^ This can be a double encoded or "normal" plutus script
73+
-> AsType (Script lang)
74+
-> m ()
75+
decodeOnlyPlutusScriptBytes _ _ scriptBytes typeProxy = do
76+
-- Decode a plutus script (double wrapped or "normal" plutus script) with the existing SerialiseAsCBOR instance for
77+
-- 'Script lang'. This should produce plutus script bytes that are not double encoded.
78+
(PlutusScriptSerialised expectedToBeValidScriptBytes) <- case deserialiseFromCBOR typeProxy scriptBytes of
79+
Left e -> failWith Nothing $ "Plutus lang: Error decoding script bytes: " ++ show e
80+
Right (SimpleScript _) -> failWith Nothing "Simple script found. Should be impossible."
81+
Right (PlutusScript _ plutusScript) -> return plutusScript
82+
83+
-- We check that the script is "runnable" and of the expected language via the
84+
-- 'PlutusScriptInEra era lang' SerialiseAsCBOR instance.
85+
(PlutusScriptSerialised confirmedToBeValidScriptBytes) <-
86+
case deserialiseFromCBOR (AsPlutusScriptInEra @era (proxyToAsType (Proxy :: Proxy lang))) $
87+
SBS.fromShort expectedToBeValidScriptBytes of
88+
Left e -> failWith Nothing $ "PlutusScriptInEra: Error decoding plutus script bytes: " ++ show e
89+
Right (PlutusScriptInEra p) -> return p
90+
91+
-- We also confirm that PlutusScriptInEra SerialiseAsCBOR instance can handle double encoded
92+
-- plutus scripts.
93+
case deserialiseFromCBOR (AsPlutusScriptInEra @era (proxyToAsType (Proxy :: Proxy lang))) scriptBytes of
94+
Left e -> failWith Nothing $ "PlutusScriptInEra: Error decoding double wrapped bytes: " ++ show e
95+
Right (PlutusScriptInEra (PlutusScriptSerialised shouldAlsoBeValidScriptBytes)) -> do
96+
confirmedToBeValidScriptBytes H.=== shouldAlsoBeValidScriptBytes
97+
98+
-- If we have fixed the double encoding issue, the bytes produced
99+
-- should be the same.
100+
expectedToBeValidScriptBytes H.=== confirmedToBeValidScriptBytes

0 commit comments

Comments
 (0)