Skip to content

Commit

Permalink
CIP-0129 support (#4879)
Browse files Browse the repository at this point in the history
<!--
Detail in a few bullet points the work accomplished in this PR.

Before you submit, don't forget to:

* Make sure the GitHub PR fields are correct:
   ✓ Set a good Title for your PR.
   ✓ Assign yourself to the PR.
   ✓ Assign one or more reviewer(s).
   ✓ Link to a Jira issue, and/or other GitHub issues or PRs.
   ✓ In the PR description delete any empty sections
     and all text commented in <!--, so that this text does not appear
     in merge commit messages.

* Don't waste reviewers' time:
   ✓ If it's a draft, select the Create Draft PR option.
✓ Self-review your changes to make sure nothing unexpected slipped
through.

* Try to make your intent clear:
   ✓ Write a good Description that explains what this PR is meant to do.
   ✓ Jira will detect and link to this PR once created, but you can also
     link this PR in the description of the corresponding Jira ticket.
   ✓ Highlight what Testing you have done.
   ✓ Acknowledge any changes required to the Documentation.
-->

The PR adds support for
https://github.com/cardano-foundation/CIPs/tree/master/CIP-0129
It adds specified in CIP-0129 prefixes for drep in case of script and
key hash credentials.
The change required to remove `drep_script` HRP that was used preciously
for drep script hashes.

All unit tests, generators therein and golden data was regenerated.

### Comments

<!-- Additional comments, links, or screenshots to attach, if any. -->

### Issue Number

fix #4855

<!-- Reference the Jira/GitHub issue that this PR relates to, and which
requirements it tackles.
  Note: Jira issues of the form ADP- will be auto-linked. -->
  • Loading branch information
abailly authored Dec 13, 2024
2 parents 80ddaf0 + 8b5d36d commit 53d41a4
Show file tree
Hide file tree
Showing 6 changed files with 99 additions and 93 deletions.
29 changes: 9 additions & 20 deletions lib/api/src/Cardano/Wallet/Api/Types/Certificate.hs
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,8 @@ import Cardano.Wallet.Primitive.Types
)
import Cardano.Wallet.Primitive.Types.DRep
( DRep (..)
, DRepID (..)
, decodeDRepKeyHashBech32
, decodeDRepScriptHashBech32
, encodeDRepKeyHashBech32
, encodeDRepScriptHashBech32
, decodeDRepIDBech32
, encodeDRepIDBech32
)
import Cardano.Wallet.Util
( ShowFmt (..)
Expand Down Expand Up @@ -343,25 +340,17 @@ mkApiAnyCertificate acct' acctPath' = \case
instance ToJSON (ApiT DRep) where
toJSON (ApiT Abstain) = "abstain"
toJSON (ApiT NoConfidence) = "no_confidence"
toJSON (ApiT (FromDRepID drep)) = case drep of
DRepFromKeyHash keyhash ->
String $ encodeDRepKeyHashBech32 keyhash
DRepFromScriptHash scripthash ->
String $ encodeDRepScriptHashBech32 scripthash
toJSON (ApiT (FromDRepID drepid)) =
String $ encodeDRepIDBech32 drepid
instance FromJSON (ApiT DRep) where
parseJSON t =
parseAbstain t <|> parseNoConfidence t <|> parseKeyHash t <|> parseScriptHash t
parseAbstain t <|> parseNoConfidence t <|> parseDrepID t
where
parseKeyHash = withText "DRepKeyHash" $ \txt ->
case decodeDRepKeyHashBech32 txt of
parseDrepID = withText "DRepID" $ \txt ->
case decodeDRepIDBech32 txt of
Left (TextDecodingError err) -> fail err
Right keyhash ->
pure $ ApiT $ FromDRepID $ DRepFromKeyHash keyhash
parseScriptHash = withText "DRepScriptHash" $ \txt ->
case decodeDRepScriptHashBech32 txt of
Left (TextDecodingError err) -> fail err
Right scripthash ->
pure $ ApiT $ FromDRepID $ DRepFromScriptHash scripthash
Right drepid ->
pure $ ApiT $ FromDRepID drepid
parseAbstain = withText "Abstain" $ \txt ->
if txt == "abstain" then
pure $ ApiT Abstain
Expand Down
111 changes: 56 additions & 55 deletions lib/primitive/lib/Cardano/Wallet/Primitive/Types/DRep.hs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{-# LANGUAGE BinaryLiterals #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingStrategies #-}
Expand All @@ -9,10 +10,10 @@ module Cardano.Wallet.Primitive.Types.DRep
, DRepKeyHash (..)
, DRepScriptHash (..)
, DRep (..)
, encodeDRepKeyHashBech32
, decodeDRepKeyHashBech32
, encodeDRepScriptHashBech32
, decodeDRepScriptHashBech32
, encodeDRepIDBech32
, decodeDRepIDBech32
, fstByteDRepKeyHash
, fstByteDRepScriptHash
)
where

Expand All @@ -21,6 +22,10 @@ import Prelude
import Control.DeepSeq
( NFData
)
import Data.Bifunctor
( first
, second
)
import Data.ByteString
( ByteString
)
Expand All @@ -32,6 +37,9 @@ import Data.Text.Class
, TextDecodingError (TextDecodingError)
, ToText (..)
)
import Data.Word
( Word8
)
import Fmt
( Buildable (..)
)
Expand All @@ -41,74 +49,80 @@ import GHC.Generics

import qualified Codec.Binary.Bech32 as Bech32
import qualified Codec.Binary.Bech32.TH as Bech32
import qualified Data.ByteString as BS

-- | Raw key hash credential
newtype DRepKeyHash = DRepKeyHash { getDRepKeyHash :: ByteString }
deriving (Generic, Eq, Ord, Show)

-- | In accordance to CIP-0129 (https://github.com/cardano-foundation/CIPs/tree/master/CIP-0129)
-- drep 0010....
-- keyhash ....0010
fstByteDRepKeyHash :: Word8
fstByteDRepKeyHash = 0b00100010

instance NFData DRepKeyHash

-- | Raw script hash credential
newtype DRepScriptHash = DRepScriptHash { getDRepScriptHash :: ByteString }
deriving (Generic, Eq, Ord, Show)

-- | In accordance to CIP-0129 (https://github.com/cardano-foundation/CIPs/tree/master/CIP-0129)
-- drep 0010....
-- scripthash ....0011
fstByteDRepScriptHash :: Word8
fstByteDRepScriptHash = 0b00100011

instance NFData DRepScriptHash

data DRepID =
DRepFromKeyHash DRepKeyHash | DRepFromScriptHash DRepScriptHash
deriving (Eq, Generic, Show, Ord)
deriving anyclass NFData

-- | Encode 'DRepKeyHash' as Bech32 with "drep" hrp.
encodeDRepKeyHashBech32 :: DRepKeyHash -> Text
encodeDRepKeyHashBech32 =
-- | Encode 'DRepID' as Bech32 with "drep" human readable prefix.
encodeDRepIDBech32 :: DRepID -> Text
encodeDRepIDBech32 drepid =
Bech32.encodeLenient hrp
. Bech32.dataPartFromBytes
. getDRepKeyHash
$ appendCip0129BytePrefix
where
hrp = [Bech32.humanReadablePart|drep|]

-- | Decode a Bech32 encoded 'DRepKeyHash'.
decodeDRepKeyHashBech32 :: Text -> Either TextDecodingError DRepKeyHash
decodeDRepKeyHashBech32 t =
appendCip0129BytePrefix = case drepid of
DRepFromKeyHash (DRepKeyHash payload) ->
BS.cons fstByteDRepKeyHash payload
DRepFromScriptHash (DRepScriptHash payload) ->
BS.cons fstByteDRepScriptHash payload

-- | Decode a Bech32 encoded 'DRepID'.
decodeDRepIDBech32 :: Text -> Either TextDecodingError DRepID
decodeDRepIDBech32 t =
case fmap Bech32.dataPartToBytes <$> Bech32.decodeLenient t of
Left _ -> Left textDecodingError
Right (hrp', Just bytes)
| hrp' == hrp -> Right $ DRepKeyHash bytes
| hrp' == hrp ->
let (fstByte, payload) = first BS.head $ BS.splitAt 1 bytes
in case fstByte of
0b00100010 ->
Right $ DRepFromKeyHash (DRepKeyHash payload)
0b00100011 ->
Right $ DRepFromScriptHash (DRepScriptHash payload)
_ ->
Left textFirstByteError
Right _ -> Left textDecodingError
where
textDecodingError = TextDecodingError $ unwords
[ "Invalid DRep key hash: expecting a Bech32 encoded value"
, "with human readable part of 'drep'."
]
hrp = [Bech32.humanReadablePart|drep|]

-- | Encode 'DRepScriptHash' as Bech32 with "drep_script" hrp.
encodeDRepScriptHashBech32 :: DRepScriptHash -> Text
encodeDRepScriptHashBech32 =
Bech32.encodeLenient hrp
. Bech32.dataPartFromBytes
. getDRepScriptHash
where
hrp = [Bech32.humanReadablePart|drep_script|]

-- | Decode a Bech32 encoded 'DRepScriptHash'.
decodeDRepScriptHashBech32 :: Text -> Either TextDecodingError DRepScriptHash
decodeDRepScriptHashBech32 t =
case fmap Bech32.dataPartToBytes <$> Bech32.decodeLenient t of
Left _ -> Left textDecodingError
Right (hrp', Just bytes)
| hrp' == hrp -> Right $ DRepScriptHash bytes
Right _ -> Left textDecodingError
where
textDecodingError = TextDecodingError $ unwords
[ "Invalid DRep Script hash: expecting a Bech32 encoded value"
, "with human readable part of 'drep_script'."
textFirstByteError = TextDecodingError $ unwords
[ "Invalid DRep metadata: expecting a byte '00100010' value for key hash or"
, "a byte '0b00100011' value for script hash."
]
hrp = [Bech32.humanReadablePart|drep_script|]
hrp = [Bech32.humanReadablePart|drep|]

instance Buildable DRepID where
build = \case
DRepFromKeyHash keyhash -> build $ encodeDRepKeyHashBech32 keyhash
DRepFromScriptHash scripthash -> build $ encodeDRepScriptHashBech32 scripthash
build = build . encodeDRepIDBech32

-- | A decentralized representation ('DRep') will
-- vote on behalf of the stake delegated to it.
Expand All @@ -122,26 +136,13 @@ data DRep
instance ToText DRep where
toText Abstain = "abstain"
toText NoConfidence = "no_confidence"
toText (FromDRepID (DRepFromKeyHash keyhash)) =
encodeDRepKeyHashBech32 keyhash
toText (FromDRepID (DRepFromScriptHash scripthash)) =
encodeDRepScriptHashBech32 scripthash
toText (FromDRepID drepid) = encodeDRepIDBech32 drepid

instance FromText DRep where
fromText txt = case txt of
"abstain" -> Right Abstain
"no_confidence" -> Right NoConfidence
_ -> case decodeDRepKeyHashBech32 txt of
Right keyhash ->
Right $ FromDRepID $ DRepFromKeyHash keyhash
Left _ -> case decodeDRepScriptHashBech32 txt of
Right scripthash ->
Right $ FromDRepID $ DRepFromScriptHash scripthash
Left _ -> Left $ TextDecodingError $ unwords
[ "I couldn't parse the given decentralized representative (DRep)."
, "I am expecting either 'abstain', 'no_confidence'"
, "or bech32 encoded drep having prefixes: 'drep'"
, "or 'drep_script'."]
_ -> second FromDRepID (decodeDRepIDBech32 txt)

instance Buildable DRep where
build = \case
Expand Down
10 changes: 5 additions & 5 deletions lib/unit/test/data/Cardano/Wallet/Api/ApiTDRep.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 18 additions & 8 deletions lib/unit/test/unit/Cardano/Wallet/Api/Malformed.hs
Original file line number Diff line number Diff line change
Expand Up @@ -215,20 +215,30 @@ instance Wellformed (PathParam ApiDRepSpecifier) where
wellformed = PathParam <$>
[ "abstain"
, "no_confidence"
, "drep15k6929drl7xt0spvudgcxndryn4kmlzpk4meed0xhqe25nle07s"
, "drep_script1hwj9yuvzxc623w5lmwvp44md7qkdywz2fcd583qmyu62jvjnz69"
, payloadKeyHashWith22HexByte
, payloadScriptHashWith23HexByte
]
where
payloadKeyHashWith22HexByte = "drep1ytje8qacj9dyua6esh86rdjqpdactf8wph05gdd72u46axcvy952y"
payloadScriptHashWith23HexByte = "drep1y0je8qacj9dyua6esh86rdjqpdactf8wph05gdd72u46axcvk492r"

instance Malformed (PathParam ApiDRepSpecifier) where
malformed = first PathParam <$>
[ (T.replicate 64 "ś", msg)
, (T.replicate 63 "1", msg)
, (T.replicate 65 "1", msg)
, ("something", msg)
, ("no-confidence", msg)
[ (T.replicate 64 "ś", msg1)
, (T.replicate 63 "1", msg1)
, (T.replicate 65 "1", msg1)
, ("something", msg1)
, ("no-confidence", msg1)
, (payloadWithoutCorrectBytePrefixCorrectHrp, msg2)
, (payloadWithWrongBytePrefixCorrectHrp, msg2)
, (payloadWithCorrectBytePrefixWrondHrp, msg1)
]
where
msg = "I couldn't parse the given decentralized representative (DRep). I am expecting either 'abstain', 'no_confidence' or bech32 encoded drep having prefixes: 'drep' or 'drep_script'."
msg1 = "Invalid DRep key hash: expecting a Bech32 encoded value with human readable part of 'drep'."
msg2 = "Invalid DRep metadata: expecting a byte '00100010' value for key hash or a byte '0b00100011' value for script hash."
payloadWithoutCorrectBytePrefixCorrectHrp = "drep15k6929drl7xt0spvudgcxndryn4kmlzpk4meed0xhqe25nle07s"
payloadWithWrongBytePrefixCorrectHrp = "drep1xhje8qacj9dyua6esh86rdjqpdactf8wph05gdd72u46axc9fjca3"
payloadWithCorrectBytePrefixWrondHrp = "drepp1ytje8qacj9dyua6esh86rdjqpdactf8wph05gdd72u46axcp60l06"

instance Wellformed (PathParam (ApiAddress ('Testnet 0))) where
wellformed = [PathParam
Expand Down
8 changes: 6 additions & 2 deletions lib/unit/test/unit/Cardano/Wallet/Api/TypesSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,8 @@ import Cardano.Wallet.Primitive.Types.DRep
, DRepID (..)
, DRepKeyHash (..)
, DRepScriptHash (..)
, fstByteDRepKeyHash
, fstByteDRepScriptHash
)
import Cardano.Wallet.Primitive.Types.Hash
( Hash (..)
Expand Down Expand Up @@ -2089,8 +2091,10 @@ instance Arbitrary ApiEncryptMetadataMethod where
instance Arbitrary DRepID where
arbitrary = do
InfiniteList bytes _ <- arbitrary
oneof [ pure $ DRepFromKeyHash $ DRepKeyHash $ BS.pack $ take 28 bytes
, pure $ DRepFromScriptHash $ DRepScriptHash $ BS.pack $ take 28 bytes
oneof [ pure $ DRepFromKeyHash $ DRepKeyHash $
BS.cons fstByteDRepKeyHash $ BS.pack $ take 28 bytes
, pure $ DRepFromScriptHash $ DRepScriptHash $
BS.cons fstByteDRepScriptHash $ BS.pack $ take 28 bytes
]

instance Arbitrary DRep where
Expand Down
8 changes: 5 additions & 3 deletions specifications/api/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -403,14 +403,16 @@ x-drepKeyHash: &drepKeyHash
type: string
format: bech32
example: drep1wqaz0q0zhtxlgn0ewssevn2mrtm30fgh2g7hr7z9rj5856457mm
description: DRep's key hash.
description: DRep's key hash according to CIP-0129.
pattern: "^(drep)1[0-9a-z]*$"

x-drepScriptHash: &drepScriptHash
type: string
format: bech32
example: drep_script1wqaz0q0zhtxlgn0ewssevn2mrtm30fgh2g7hr7z9rj5856457mm
description: DRep's script hash.
example: drep1wqaz0q0zhtxlgn0ewssevn2mrtm30fgh2g7hr7z9rj5856457mm
description: |
DRep's script hash according to CIP-0129 uses also 'drep' bech32 prefix.
This one is deprecated and should not be used.
pattern: "^(drep_script)1[0-9a-z]*$"

x-walletAccountXPubkey: &walletAccountXPubkey
Expand Down

0 comments on commit 53d41a4

Please sign in to comment.