Skip to content

Commit

Permalink
Accept Bech32 verification keys in Shelley node CLI
Browse files Browse the repository at this point in the history
  • Loading branch information
intricate committed Sep 25, 2020
1 parent 5e71bbc commit 9cdb637
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 27 deletions.
20 changes: 17 additions & 3 deletions cardano-cli/src/Cardano/CLI/Shelley/Commands.hs
Expand Up @@ -42,6 +42,7 @@ module Cardano.CLI.Shelley.Commands
, PrivKeyFile (..)
, BlockId (..)
, WitnessSigningData (..)
, ColdVerificationKeyOrFile (..)
) where

import Data.Text (Text)
Expand Down Expand Up @@ -206,9 +207,9 @@ data NodeCmd
= NodeKeyGenCold VerificationKeyFile SigningKeyFile OpCertCounterFile
| NodeKeyGenKES VerificationKeyFile SigningKeyFile
| NodeKeyGenVRF VerificationKeyFile SigningKeyFile
| NodeKeyHashVRF VerificationKeyFile (Maybe OutputFile)
| NodeNewCounter VerificationKeyFile Word OpCertCounterFile
| NodeIssueOpCert VerificationKeyFile SigningKeyFile OpCertCounterFile
| NodeKeyHashVRF (VerificationKeyOrFile VrfKey) (Maybe OutputFile)
| NodeNewCounter ColdVerificationKeyOrFile Word OpCertCounterFile
| NodeIssueOpCert (VerificationKeyOrFile KesKey) SigningKeyFile OpCertCounterFile
KESPeriod OutputFile
deriving (Eq, Show)

Expand Down Expand Up @@ -456,3 +457,16 @@ data WitnessSigningData
-- from the address and used in the construction of the Byron witness.
| ScriptWitnessSigningData !ScriptFile
deriving (Eq, Show)

-- | Either a stake pool verification key, genesis delegate verification key,
-- or a path to a cold verification key file.
--
-- Note that a "cold verification key" refers to either a stake pool or
-- genesis delegate verification key.
--
-- TODO: A genesis delegate extended key should also be valid here.
data ColdVerificationKeyOrFile
= ColdStakePoolVerificationKey !(VerificationKey StakePoolKey)
| ColdGenesisDelegateVerificationKey !(VerificationKey GenesisDelegateKey)
| ColdVerificationKeyFile !VerificationKeyFile
deriving (Eq, Show)
87 changes: 82 additions & 5 deletions cardano-cli/src/Cardano/CLI/Shelley/Parsers.hs
@@ -1,3 +1,5 @@
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ScopedTypeVariables #-}

module Cardano.CLI.Shelley.Parsers
( -- * CLI command parser
Expand Down Expand Up @@ -559,11 +561,11 @@ pNodeCmd =

pKeyHashVRF :: Parser NodeCmd
pKeyHashVRF =
NodeKeyHashVRF <$> pVerificationKeyFile Input <*> pMaybeOutputFile
NodeKeyHashVRF <$> pVerificationKeyOrFile AsVrfKey <*> pMaybeOutputFile

pNewCounter :: Parser NodeCmd
pNewCounter =
NodeNewCounter <$> pColdVerificationKeyFile
NodeNewCounter <$> pColdVerificationKeyOrFile
<*> pCounterValue
<*> pOperatorCertIssueCounterFile

Expand All @@ -577,7 +579,7 @@ pNodeCmd =

pIssueOpCert :: Parser NodeCmd
pIssueOpCert =
NodeIssueOpCert <$> pKESVerificationKeyFile
NodeIssueOpCert <$> pKesVerificationKeyOrFile
<*> pColdSigningKeyFile
<*> pOperatorCertIssueCounterFile
<*> pKesPeriod
Expand Down Expand Up @@ -1165,6 +1167,12 @@ pOutputFile =
<> Opt.completer (Opt.bashCompleter "file")
)

pColdVerificationKeyOrFile :: Parser ColdVerificationKeyOrFile
pColdVerificationKeyOrFile =
ColdStakePoolVerificationKey <$> pStakePoolVerificationKey
<|> ColdGenesisDelegateVerificationKey <$> pGenesisDelegateVerificationKey
<|> ColdVerificationKeyFile <$> pColdVerificationKeyFile

pColdVerificationKeyFile :: Parser VerificationKeyFile
pColdVerificationKeyFile =
VerificationKeyFile <$>
Expand All @@ -1181,6 +1189,41 @@ pColdVerificationKeyFile =
)
)

pVerificationKey
:: forall keyrole.
( HasTextEnvelope (VerificationKey keyrole)
, SerialiseAsBech32 (VerificationKey keyrole)
)
=> AsType keyrole
-> Parser (VerificationKey keyrole)
pVerificationKey asType =
Opt.option
(Opt.eitherReader deserialiseFromBech32OrHex)
( Opt.long "verification-key"
<> Opt.metavar "STRING"
<> Opt.help "Verification key (Bech32 or hex-encoded)."
)
where
keyFormats :: NonEmpty KeyFormat
keyFormats = NE.fromList [KeyFormatBech32, KeyFormatHex]

deserialiseFromBech32OrHex
:: String
-> Either String (VerificationKey keyrole)
deserialiseFromBech32OrHex str =
first (Text.unpack . renderKeyDecodeError) $
deserialiseKey (AsVerificationKey asType) keyFormats (BSC.pack str)

pVerificationKeyOrFile
:: ( HasTextEnvelope (VerificationKey keyrole)
, SerialiseAsBech32 (VerificationKey keyrole)
)
=> AsType keyrole
-> Parser (VerificationKeyOrFile keyrole)
pVerificationKeyOrFile asType =
VerificationKeyValue <$> pVerificationKey asType
<|> VerificationKeyFilePath <$> pVerificationKeyFile Input

pVerificationKeyFile :: FileDirection -> Parser VerificationKeyFile
pVerificationKeyFile fdir =
VerificationKeyFile <$>
Expand Down Expand Up @@ -1305,8 +1348,42 @@ pGenesisDelegateVerificationKeyOrHashOrFile =
VerificationKeyOrFile <$> pGenesisDelegateVerificationKeyOrFile
<|> VerificationKeyHash <$> pGenesisDelegateVerificationKeyHash

pKESVerificationKeyFile :: Parser VerificationKeyFile
pKESVerificationKeyFile =
pKesVerificationKeyOrFile :: Parser (VerificationKeyOrFile KesKey)
pKesVerificationKeyOrFile =
VerificationKeyValue <$> pKesVerificationKey
<|> VerificationKeyFilePath <$> pKesVerificationKeyFile

pKesVerificationKey :: Parser (VerificationKey KesKey)
pKesVerificationKey =
Opt.option
(Opt.eitherReader deserialiseVerKey)
( Opt.long "kes-verification-key"
<> Opt.metavar "STRING"
<> Opt.help "A Bech32 or hex-encoded hot KES verification key."
)
where
asType :: AsType (VerificationKey KesKey)
asType = AsVerificationKey AsKesKey

deserialiseVerKey :: String -> Either String (VerificationKey KesKey)
deserialiseVerKey str =
case deserialiseFromBech32 asType (Text.pack str) of
Right res -> Right res

-- The input was valid Bech32, but some other error occurred.
Left err@(Bech32UnexpectedPrefix _ _) -> Left (displayError err)
Left err@(Bech32DataPartToBytesError _) -> Left (displayError err)
Left err@(Bech32DeserialiseFromBytesError _) -> Left (displayError err)
Left err@(Bech32WrongPrefix _ _) -> Left (displayError err)

-- The input was not valid Bech32. Attempt to deserialize it as hex.
Left (Bech32DecodingError _) ->
case deserialiseFromRawBytesHex asType (BSC.pack str) of
Just res' -> Right res'
Nothing -> Left "Invalid stake pool verification key."

pKesVerificationKeyFile :: Parser VerificationKeyFile
pKesVerificationKeyFile =
VerificationKeyFile <$>
( Opt.strOption
( Opt.long "kes-verification-key-file"
Expand Down
53 changes: 34 additions & 19 deletions cardano-cli/src/Cardano/CLI/Shelley/Run/Node.hs
Expand Up @@ -7,7 +7,8 @@ module Cardano.CLI.Shelley.Run.Node
import Cardano.Api.TextView (TextViewDescription (..))
import Cardano.Api.Typed
import Cardano.CLI.Shelley.Commands
import Cardano.CLI.Shelley.Key (KeyDecodeError (..), readSigningKeyFileAnyOf)
import Cardano.CLI.Shelley.Key (KeyDecodeError, VerificationKeyOrFile,
readSigningKeyFileAnyOf, readVerificationKeyOrFile)
import Cardano.CLI.Types (SigningKeyFile (..), VerificationKeyFile (..))
import Cardano.Prelude
import Control.Monad.Trans.Except (ExceptT)
Expand All @@ -31,8 +32,7 @@ renderShelleyNodeCmdError err =
case err of
ShelleyNodeCmdReadFileError fileErr -> Text.pack (displayError fileErr)

ShelleyNodeCmdReadKeyFileError fileErr ->
Text.pack (displayError fileErr)
ShelleyNodeCmdReadKeyFileError fileErr -> Text.pack (displayError fileErr)

ShelleyNodeCmdWriteFileError fileErr -> Text.pack (displayError fileErr)

Expand Down Expand Up @@ -117,12 +117,13 @@ runNodeKeyGenVRF (VerificationKeyFile vkeyPath) (SigningKeyFile skeyPath) = do
skeyDesc = TextViewDescription "VRF Signing Key"
vkeyDesc = TextViewDescription "VRF Verification Key"

runNodeKeyHashVRF :: VerificationKeyFile -> Maybe OutputFile
runNodeKeyHashVRF :: VerificationKeyOrFile VrfKey
-> Maybe OutputFile
-> ExceptT ShelleyNodeCmdError IO ()
runNodeKeyHashVRF (VerificationKeyFile vkeyPath) mOutputFp = do
vkey <- firstExceptT ShelleyNodeCmdReadFileError
runNodeKeyHashVRF verKeyOrFile mOutputFp = do
vkey <- firstExceptT ShelleyNodeCmdReadKeyFileError
. newExceptT
$ readFileTextEnvelope (AsVerificationKey AsVrfKey) vkeyPath
$ readVerificationKeyOrFile AsVrfKey verKeyOrFile

let hexKeyHash = serialiseToRawBytesHex (verificationKeyHash vkey)

Expand All @@ -131,20 +132,15 @@ runNodeKeyHashVRF (VerificationKeyFile vkeyPath) mOutputFp = do
Nothing -> liftIO $ BS.putStrLn hexKeyHash


runNodeNewCounter :: VerificationKeyFile
runNodeNewCounter :: ColdVerificationKeyOrFile
-> Word
-> OpCertCounterFile
-> ExceptT ShelleyNodeCmdError IO ()
runNodeNewCounter (VerificationKeyFile vkeyPath) counter
runNodeNewCounter coldVerKeyOrFile counter
(OpCertCounterFile ocertCtrPath) = do

vkey <- firstExceptT ShelleyNodeCmdReadFileError . newExceptT $
readFileTextEnvelopeAnyOf
[ FromSomeType (AsVerificationKey AsStakePoolKey) id
, FromSomeType (AsVerificationKey AsGenesisDelegateKey)
castVerificationKey
]
vkeyPath
readColdVerificationKeyOrFile coldVerKeyOrFile

let ocertIssueCounter =
OperationalCertificateIssueCounter (fromIntegral counter) vkey
Expand All @@ -153,7 +149,7 @@ runNodeNewCounter (VerificationKeyFile vkeyPath) counter
writeFileTextEnvelope ocertCtrPath Nothing ocertIssueCounter


runNodeIssueOpCert :: VerificationKeyFile
runNodeIssueOpCert :: VerificationKeyOrFile KesKey
-- ^ This is the hot KES verification key.
-> SigningKeyFile
-- ^ This is the cold signing key.
Expand All @@ -164,7 +160,7 @@ runNodeIssueOpCert :: VerificationKeyFile
-- ^ Start of the validity period for this certificate.
-> OutputFile
-> ExceptT ShelleyNodeCmdError IO ()
runNodeIssueOpCert (VerificationKeyFile vkeyKesPath)
runNodeIssueOpCert kesVerKeyOrFile
stakePoolSKeyFile
(OpCertCounterFile ocertCtrPath)
kesPeriod
Expand All @@ -174,9 +170,9 @@ runNodeIssueOpCert (VerificationKeyFile vkeyKesPath)
. newExceptT
$ readFileTextEnvelope AsOperationalCertificateIssueCounter ocertCtrPath

verKeyKes <- firstExceptT ShelleyNodeCmdReadFileError
verKeyKes <- firstExceptT ShelleyNodeCmdReadKeyFileError
. newExceptT
$ readFileTextEnvelope (AsVerificationKey AsKesKey) vkeyKesPath
$ readVerificationKeyOrFile AsKesKey kesVerKeyOrFile

signKey <- firstExceptT ShelleyNodeCmdReadKeyFileError
. newExceptT
Expand Down Expand Up @@ -229,3 +225,22 @@ runNodeIssueOpCert (VerificationKeyFile vkeyKesPath)
(SigningKey GenesisDelegateExtendedKey))]
bech32PossibleBlockIssuers =
[FromSomeType (AsSigningKey AsStakePoolKey) Left]

-- | Read a cold verification key or file.
--
-- If a filepath is provided, it will be interpreted as a text envelope
-- formatted file.
readColdVerificationKeyOrFile
:: ColdVerificationKeyOrFile
-> IO (Either (FileError TextEnvelopeError) (VerificationKey StakePoolKey))
readColdVerificationKeyOrFile coldVerKeyOrFile =
case coldVerKeyOrFile of
ColdStakePoolVerificationKey vk -> pure (Right vk)
ColdGenesisDelegateVerificationKey vk ->
pure $ Right (castVerificationKey vk)
ColdVerificationKeyFile (VerificationKeyFile fp) ->
readFileTextEnvelopeAnyOf
[ FromSomeType (AsVerificationKey AsStakePoolKey) id
, FromSomeType (AsVerificationKey AsGenesisDelegateKey) castVerificationKey
]
fp

0 comments on commit 9cdb637

Please sign in to comment.