packages feed

cardano-addresses-4.0.0: lib/Command/Key/Hash.hs

{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}

{-# OPTIONS_HADDOCK hide #-}

-- |
-- Copyright: 2020 Input Output (Hong Kong) Ltd., 2021-2022 Input Output Global Inc. (IOG), 2023-2025 Intersect
-- License: Apache-2.0

module Command.Key.Hash
    ( Cmd (..)
    , mod
    , run
    ) where

import Prelude hiding
    ( mod )

import Cardano.Address.Derivation
    ( hashCredential )
import Cardano.Address.KeyHash
    ( GovernanceType, KeyHash (..), KeyRole (..), keyHashToText )
import Control.Monad
    ( when )
import Data.Maybe
    ( fromJust )
import Data.Text
    ( Text )
import Options.Applicative
    ( CommandFields, Mod, command, footerDoc, helper, info, progDesc )
import Options.Applicative.Governance
    ( governanceOpt )
import Options.Applicative.Help.Pretty
    ( pretty )
import System.IO
    ( stdin, stdout )
import System.IO.Extra
    ( hGetBech32, hPutStringNoNewLn )

import qualified Cardano.Codec.Bech32.Prefixes as CIP5
import qualified Data.ByteString as BS
import qualified Data.Text as T

newtype Cmd = Hash
    { govType :: GovernanceType
    } deriving (Show)

mod :: (Cmd -> parent) -> Mod CommandFields parent
mod liftCmd = command "hash" $
    info (helper <*> fmap liftCmd parser) $ mempty
        <> progDesc "Get the hash of a public key"
        <> footerDoc (Just $ pretty $ mconcat
            [ "The public key is read from stdin and" :: Text
            , "bech32-encoded hash is returned."
            , "To get hex-encoded output pass it to stdin of `bech32`."
            ])
  where
    parser = Hash
        <$> governanceOpt

run :: Cmd -> IO ()
run Hash{govType} = do
    (hrp, bytes) <- hGetBech32 stdin allowedPrefixes
    guardBytes hrp bytes
    let keyhash = KeyHash (prefixFor hrp) (hashCredential $ BS.take 32 bytes)
    hPutStringNoNewLn stdout $ T.unpack $
        keyHashToText keyhash govType
  where
    -- Mapping of input HRP to key role
    prefixToRole =
        [ ( CIP5.addr_vk         , Payment )
        , ( CIP5.addr_xvk        , Payment )
        , ( CIP5.stake_vk        , Delegation )
        , ( CIP5.stake_xvk       , Delegation )
        , ( CIP5.addr_shared_vk  , PaymentShared )
        , ( CIP5.addr_shared_xvk , PaymentShared )
        , ( CIP5.stake_shared_vk , DelegationShared )
        , ( CIP5.stake_shared_xvk, DelegationShared )
        , ( CIP5.policy_vk       , Policy )
        , ( CIP5.policy_xvk      , Policy )
        , ( CIP5.drep_vk         , Representative )
        , ( CIP5.drep_xvk        , Representative )
        , ( CIP5.cc_cold_vk      , CommitteeCold )
        , ( CIP5.cc_cold_xvk     , CommitteeCold )
        , ( CIP5.cc_hot_vk       , CommitteeHot )
        , ( CIP5.cc_hot_xvk      , CommitteeHot )
        ]
    allowedPrefixes = map fst prefixToRole
    prefixFor = fromJust . flip lookup prefixToRole

    extendedPrefixes =
        [ CIP5.addr_xvk
        , CIP5.stake_xvk
        , CIP5.addr_shared_xvk
        , CIP5.stake_shared_xvk
        , CIP5.policy_xvk
        , CIP5.drep_xvk
        , CIP5.cc_cold_xvk
        , CIP5.cc_hot_xvk
        ]

    guardBytes hrp bytes
        | hrp `elem` extendedPrefixes = do
            when (BS.length bytes /= 64) $
                fail "data should be a 32-byte public key with a 32-byte chain-code appended"

        | otherwise = do
            when (BS.length bytes /= 32) $
                fail "data should be a 32-byte public key."