diff --git a/lib/api/src/Cardano/Wallet/Api/Types/Certificate.hs b/lib/api/src/Cardano/Wallet/Api/Types/Certificate.hs index 67cbe2bf0e9..52671e88926 100644 --- a/lib/api/src/Cardano/Wallet/Api/Types/Certificate.hs +++ b/lib/api/src/Cardano/Wallet/Api/Types/Certificate.hs @@ -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 (..) @@ -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 diff --git a/lib/primitive/lib/Cardano/Wallet/Primitive/Types/DRep.hs b/lib/primitive/lib/Cardano/Wallet/Primitive/Types/DRep.hs index 3c03047e485..ca2118bd5c3 100644 --- a/lib/primitive/lib/Cardano/Wallet/Primitive/Types/DRep.hs +++ b/lib/primitive/lib/Cardano/Wallet/Primitive/Types/DRep.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE BinaryLiterals #-} {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DerivingStrategies #-} @@ -9,10 +10,10 @@ module Cardano.Wallet.Primitive.Types.DRep , DRepKeyHash (..) , DRepScriptHash (..) , DRep (..) - , encodeDRepKeyHashBech32 - , decodeDRepKeyHashBech32 - , encodeDRepScriptHashBech32 - , decodeDRepScriptHashBech32 + , encodeDRepIDBech32 + , decodeDRepIDBech32 + , fstByteDRepKeyHash + , fstByteDRepScriptHash ) where @@ -21,6 +22,10 @@ import Prelude import Control.DeepSeq ( NFData ) +import Data.Bifunctor + ( first + , second + ) import Data.ByteString ( ByteString ) @@ -32,6 +37,9 @@ import Data.Text.Class , TextDecodingError (TextDecodingError) , ToText (..) ) +import Data.Word + ( Word8 + ) import Fmt ( Buildable (..) ) @@ -41,15 +49,30 @@ 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 = @@ -57,58 +80,49 @@ data DRepID = 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. @@ -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 diff --git a/lib/unit/test/data/Cardano/Wallet/Api/ApiTDRep.json b/lib/unit/test/data/Cardano/Wallet/Api/ApiTDRep.json index 6514ebc74ba..4f8914a32bd 100644 --- a/lib/unit/test/data/Cardano/Wallet/Api/ApiTDRep.json +++ b/lib/unit/test/data/Cardano/Wallet/Api/ApiTDRep.json @@ -2,14 +2,14 @@ "samples": [ "abstain", "abstain", - "drep1c9umxe06vc0vzv9fu866axles2lpaqhsh36suqc7h0hzv8e2hcs", - "no_confidence", - "abstain", "abstain", - "drep_script13y9jcl4mu3sxr6t2dckyceuz6mwtaahqyr8dld8yqhqmwjv0l9e", + "drep1yv3cj5rqf0xtv7t77v30dyvm25umvhku76td23z4yf7xllsl0cw64x", "abstain", + "drep1yv3mrqjjtrekq8hjskdlv7dfmwq3s93urkff6d6v344w9hjj9kxc0k", "no_confidence", + "abstain", + "drep1yg3q2x8ph8ujl6eqm85ej9a4f69qw8dujcnvf9w2kry3jsa0nmwjfz", "abstain" ], - "seed": -2053978213 + "seed": -323609406 } \ No newline at end of file diff --git a/lib/unit/test/unit/Cardano/Wallet/Api/Malformed.hs b/lib/unit/test/unit/Cardano/Wallet/Api/Malformed.hs index fdb05cf1977..a059a4d12c6 100644 --- a/lib/unit/test/unit/Cardano/Wallet/Api/Malformed.hs +++ b/lib/unit/test/unit/Cardano/Wallet/Api/Malformed.hs @@ -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 diff --git a/lib/unit/test/unit/Cardano/Wallet/Api/TypesSpec.hs b/lib/unit/test/unit/Cardano/Wallet/Api/TypesSpec.hs index 7eba242c605..e03abc0dfca 100644 --- a/lib/unit/test/unit/Cardano/Wallet/Api/TypesSpec.hs +++ b/lib/unit/test/unit/Cardano/Wallet/Api/TypesSpec.hs @@ -395,6 +395,8 @@ import Cardano.Wallet.Primitive.Types.DRep , DRepID (..) , DRepKeyHash (..) , DRepScriptHash (..) + , fstByteDRepKeyHash + , fstByteDRepScriptHash ) import Cardano.Wallet.Primitive.Types.Hash ( Hash (..) @@ -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 diff --git a/specifications/api/swagger.yaml b/specifications/api/swagger.yaml index bd3634535f0..aeb27a75725 100644 --- a/specifications/api/swagger.yaml +++ b/specifications/api/swagger.yaml @@ -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