-
Notifications
You must be signed in to change notification settings - Fork 720
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add command for converting Byron, Icarus, and Shelley style cardano-address signing keys #1822
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,7 +7,7 @@ module Cardano.CLI.Shelley.Run.Key | |
, runKeyCmd | ||
|
||
-- * Exports for testing | ||
, decodeBech32Key | ||
, decodeBech32 | ||
) where | ||
|
||
import Cardano.Prelude | ||
|
@@ -32,7 +32,7 @@ import qualified Cardano.Crypto.Signing as Byron | |
import qualified Shelley.Spec.Ledger.Keys as Shelley | ||
|
||
import Cardano.Api.Shelley.ITN (xprvFromBytes) | ||
import Cardano.Api.Typed hiding (Bech32DecodeError (..)) | ||
import Cardano.Api.Typed | ||
|
||
import Cardano.CLI.Byron.Key (CardanoEra (..)) | ||
import qualified Cardano.CLI.Byron.Key as Byron | ||
|
@@ -51,8 +51,10 @@ data ShelleyKeyCmdError | |
!Text | ||
-- ^ Text representation of the parse error. Unfortunately, the actual | ||
-- error type isn't exported. | ||
| ShelleyKeyCmdItnKeyConvError !ConversionError | ||
| ShelleyKeyCmdItnKeyConvError !ItnKeyConversionError | ||
| ShelleyKeyCmdWrongKeyTypeError | ||
| ShelleyKeyCmdCardanoAddressSigningKeyFileError | ||
!(FileError CardanoAddressSigningKeyConversionError) | ||
deriving Show | ||
|
||
renderShelleyKeyCmdError :: ShelleyKeyCmdError -> Text | ||
|
@@ -66,6 +68,8 @@ renderShelleyKeyCmdError err = | |
ShelleyKeyCmdItnKeyConvError convErr -> renderConversionError convErr | ||
ShelleyKeyCmdWrongKeyTypeError -> Text.pack "Please use a signing key file \ | ||
\when converting ITN BIP32 or Extended keys" | ||
ShelleyKeyCmdCardanoAddressSigningKeyFileError fileErr -> | ||
Text.pack (displayError fileErr) | ||
|
||
runKeyCmd :: KeyCmd -> ExceptT ShelleyKeyCmdError IO () | ||
runKeyCmd cmd = | ||
|
@@ -89,6 +93,8 @@ runKeyCmd cmd = | |
KeyConvertITNBip32ToStakeKey itnPrivKeyFile outFile -> | ||
runConvertITNBip32ToStakeKey itnPrivKeyFile outFile | ||
|
||
KeyConvertCardanoAddressSigningKey keyType skfOld skfNew -> | ||
runConvertCardanoAddressSigningKey keyType skfOld skfNew | ||
|
||
runGetVerificationKey :: SigningKeyFile | ||
-> VerificationKeyFile | ||
|
@@ -438,86 +444,202 @@ runConvertITNBip32ToStakeKey (ASigningKeyFile (SigningKeyFile sk)) (OutputFile o | |
firstExceptT ShelleyKeyCmdWriteFileError . newExceptT | ||
$ writeFileTextEnvelope outFile Nothing skey | ||
|
||
data ConversionError | ||
= Bech32DecodingError | ||
-- ^ Bech32 key | ||
!Text | ||
!Bech32.DecodingError | ||
| Bech32ErrorExtractingByes !Bech32.DataPart | ||
| Bech32ReadError !FilePath !IOException | ||
| ITNError !Bech32.HumanReadablePart !Bech32.DataPart | ||
| SigningKeyDeserializationError !ByteString | ||
| VerificationKeyDeserializationError !ByteString | ||
-- | An error that can occur while converting an Incentivized Testnet (ITN) | ||
-- key. | ||
data ItnKeyConversionError | ||
= ItnKeyBech32DecodeError !Bech32DecodeError | ||
| ItnReadBech32FileError !FilePath !IOException | ||
| ItnSigningKeyDeserialisationError !ByteString | ||
| ItnVerificationKeyDeserialisationError !ByteString | ||
deriving Show | ||
|
||
renderConversionError :: ConversionError -> Text | ||
-- | Render an error message for an 'ItnKeyConversionError'. | ||
renderConversionError :: ItnKeyConversionError -> Text | ||
renderConversionError err = | ||
case err of | ||
Bech32DecodingError key decErr -> | ||
"Error decoding Bech32 key: " <> key <> " Error: " <> textShow decErr | ||
Bech32ErrorExtractingByes dp -> | ||
"Unable to extract bytes from: " <> Bech32.dataPartToText dp | ||
Bech32ReadError fp readErr -> | ||
"Error reading bech32 key at: " <> textShow fp | ||
ItnKeyBech32DecodeError decErr -> | ||
"Error decoding Bech32 key: " <> Text.pack (displayError decErr) | ||
ItnReadBech32FileError fp readErr -> | ||
"Error reading Bech32 key at: " <> textShow fp | ||
<> " Error: " <> Text.pack (displayException readErr) | ||
ITNError hRpart dp -> | ||
"Error extracting a ByteString from DataPart: " <> Bech32.dataPartToText dp <> | ||
" With human readable part: " <> Bech32.humanReadablePartToText hRpart | ||
SigningKeyDeserializationError sKey -> | ||
"Error deserialising signing key: " <> textShow (BSC.unpack sKey) | ||
VerificationKeyDeserializationError vKey -> | ||
ItnSigningKeyDeserialisationError _sKey -> | ||
-- Sensitive data, such as the signing key, is purposely not included in | ||
-- the error message. | ||
"Error deserialising signing key." | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm curious about this concern since presumably the user has access to the key that might be printed anyway. Might it be worthwhile printing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It'd be a concern if, for example, the user is redirecting stderr to some log file with more lax permissions than the signing key file itself. I think it could be useful to print a suffix, but I wonder how many bytes of the signing key would be safe to display. |
||
ItnVerificationKeyDeserialisationError vKey -> | ||
"Error deserialising verification key: " <> textShow (BSC.unpack vKey) | ||
|
||
-- | Convert public ed25519 key to a Shelley stake verification key | ||
convertITNVerificationKey :: Text -> Either ConversionError (VerificationKey StakeKey) | ||
convertITNVerificationKey :: Text -> Either ItnKeyConversionError (VerificationKey StakeKey) | ||
convertITNVerificationKey pubKey = do | ||
(_, _, keyBS) <- decodeBech32Key pubKey | ||
(_, _, keyBS) <- first ItnKeyBech32DecodeError (decodeBech32 pubKey) | ||
case DSIGN.rawDeserialiseVerKeyDSIGN keyBS of | ||
Just verKey -> Right . StakeVerificationKey $ Shelley.VKey verKey | ||
Nothing -> Left $ VerificationKeyDeserializationError keyBS | ||
Nothing -> Left $ ItnVerificationKeyDeserialisationError keyBS | ||
|
||
-- | Convert private ed22519 key to a Shelley signing key. | ||
convertITNSigningKey :: Text -> Either ConversionError (SigningKey StakeKey) | ||
convertITNSigningKey :: Text -> Either ItnKeyConversionError (SigningKey StakeKey) | ||
convertITNSigningKey privKey = do | ||
(_, _, keyBS) <- decodeBech32Key privKey | ||
(_, _, keyBS) <- first ItnKeyBech32DecodeError (decodeBech32 privKey) | ||
case DSIGN.rawDeserialiseSignKeyDSIGN keyBS of | ||
Just signKey -> Right $ StakeSigningKey signKey | ||
Nothing -> Left $ SigningKeyDeserializationError keyBS | ||
Nothing -> Left $ ItnSigningKeyDeserialisationError keyBS | ||
|
||
-- | Convert extended private ed22519 key to a Shelley signing key | ||
-- Extended private key = 64 bytes, | ||
-- Public key = 32 bytes. | ||
convertITNExtendedSigningKey :: Text -> Either ConversionError (SigningKey StakeExtendedKey) | ||
convertITNExtendedSigningKey :: Text -> Either ItnKeyConversionError (SigningKey StakeExtendedKey) | ||
convertITNExtendedSigningKey privKey = do | ||
(_, _, privkeyBS) <- decodeBech32Key privKey | ||
(_, _, privkeyBS) <- first ItnKeyBech32DecodeError (decodeBech32 privKey) | ||
let dummyChainCode = BS.replicate 32 0 | ||
case xprvFromBytes $ BS.concat [privkeyBS, dummyChainCode] of | ||
Just xprv -> Right $ StakeExtendedSigningKey xprv | ||
Nothing -> Left $ SigningKeyDeserializationError privkeyBS | ||
Nothing -> Left $ ItnSigningKeyDeserialisationError privkeyBS | ||
|
||
-- BIP32 Private key = 96 bytes (64 bytes extended private key + 32 bytes chaincode) | ||
-- BIP32 Public Key = 64 Bytes | ||
convertITNBIP32SigningKey :: Text -> Either ConversionError (SigningKey StakeExtendedKey) | ||
convertITNBIP32SigningKey :: Text -> Either ItnKeyConversionError (SigningKey StakeExtendedKey) | ||
convertITNBIP32SigningKey privKey = do | ||
(_, _, privkeyBS) <- decodeBech32Key privKey | ||
(_, _, privkeyBS) <- first ItnKeyBech32DecodeError (decodeBech32 privKey) | ||
case xprvFromBytes privkeyBS of | ||
Just xprv -> Right $ StakeExtendedSigningKey xprv | ||
Nothing -> Left $ SigningKeyDeserializationError privkeyBS | ||
|
||
-- | Convert ITN Bech32 public or private keys to 'ByteString's | ||
decodeBech32Key :: Text | ||
-> Either ConversionError | ||
(Bech32.HumanReadablePart, Bech32.DataPart, ByteString) | ||
decodeBech32Key key = | ||
case Bech32.decodeLenient key of | ||
Left err -> Left $ Bech32DecodingError key err | ||
Right (hRpart, dataPart) -> case Bech32.dataPartToBytes dataPart of | ||
Nothing -> Left $ ITNError hRpart dataPart | ||
Just bs -> Right (hRpart, dataPart, bs) | ||
|
||
readFileITNKey :: FilePath -> IO (Either ConversionError Text) | ||
Nothing -> Left $ ItnSigningKeyDeserialisationError privkeyBS | ||
|
||
readFileITNKey :: FilePath -> IO (Either ItnKeyConversionError Text) | ||
readFileITNKey fp = do | ||
eStr <- Exception.try $ readFile fp | ||
case eStr of | ||
Left e -> return . Left $ Bech32ReadError fp e | ||
Left e -> return . Left $ ItnReadBech32FileError fp e | ||
Right str -> return . Right . Text.concat $ Text.words str | ||
|
||
-------------------------------------------------------------------------------- | ||
-- `cardano-address` extended signing key conversions | ||
-------------------------------------------------------------------------------- | ||
|
||
runConvertCardanoAddressSigningKey | ||
:: CardanoAddressKeyType | ||
-> SigningKeyFile | ||
-> OutputFile | ||
-> ExceptT ShelleyKeyCmdError IO () | ||
runConvertCardanoAddressSigningKey keyType skFile (OutputFile outFile) = do | ||
sKey <- firstExceptT ShelleyKeyCmdCardanoAddressSigningKeyFileError | ||
. newExceptT | ||
$ readSomeCardanoAddressSigningKeyFile keyType skFile | ||
firstExceptT ShelleyKeyCmdWriteFileError . newExceptT | ||
$ writeSomeCardanoAddressSigningKeyFile outFile sKey | ||
|
||
-- | Some kind of signing key that was converted from a @cardano-address@ | ||
-- signing key. | ||
data SomeCardanoAddressSigningKey | ||
= ACardanoAddrShelleyPaymentSigningKey !(SigningKey PaymentExtendedKey) | ||
| ACardanoAddrShelleyStakeSigningKey !(SigningKey StakeExtendedKey) | ||
| ACardanoAddrByronSigningKey !(SigningKey ByronKey) | ||
|
||
-- | An error that can occur while converting a @cardano-address@ extended | ||
-- signing key. | ||
data CardanoAddressSigningKeyConversionError | ||
= CardanoAddressSigningKeyBech32DecodeError !Bech32DecodeError | ||
-- ^ There was an error in decoding the string as Bech32. | ||
| CardanoAddressSigningKeyDeserialisationError !ByteString | ||
-- ^ There was an error in converting the @cardano-address@ extended signing | ||
-- key. | ||
deriving (Show, Eq) | ||
|
||
instance Error CardanoAddressSigningKeyConversionError where | ||
displayError = Text.unpack . renderCardanoAddressSigningKeyConversionError | ||
|
||
-- | Render an error message for a 'CardanoAddressSigningKeyConversionError'. | ||
renderCardanoAddressSigningKeyConversionError | ||
:: CardanoAddressSigningKeyConversionError | ||
-> Text | ||
renderCardanoAddressSigningKeyConversionError err = | ||
case err of | ||
CardanoAddressSigningKeyBech32DecodeError decErr -> | ||
Text.pack (displayError decErr) | ||
CardanoAddressSigningKeyDeserialisationError _bs -> | ||
intricate marked this conversation as resolved.
Show resolved
Hide resolved
|
||
-- Sensitive data, such as the signing key, is purposely not included in | ||
-- the error message. | ||
"Error deserialising cardano-address signing key." | ||
|
||
-- | Decode a Bech32-encoded string. | ||
decodeBech32 | ||
:: Text | ||
-> Either Bech32DecodeError (Bech32.HumanReadablePart, Bech32.DataPart, ByteString) | ||
decodeBech32 bech32Str = | ||
case Bech32.decodeLenient bech32Str of | ||
Left err -> Left (Bech32DecodingError err) | ||
Right (hrPart, dataPart) -> | ||
case Bech32.dataPartToBytes dataPart of | ||
Nothing -> | ||
Left $ Bech32DataPartToBytesError (Bech32.dataPartToText dataPart) | ||
Just bs -> Right (hrPart, dataPart, bs) | ||
|
||
-- | Convert a Ed25519 BIP32 extended signing key (96 bytes) to a @cardano-crypto@ | ||
-- style extended signing key. | ||
-- | ||
-- Note that both the ITN and @cardano-address@ use this key format. | ||
convertBip32SigningKey | ||
:: ByteString | ||
-> Either CardanoAddressSigningKeyConversionError Crypto.XPrv | ||
convertBip32SigningKey signingKeyBs = | ||
case xprvFromBytes signingKeyBs of | ||
Just xPrv -> Right xPrv | ||
Nothing -> | ||
Left $ CardanoAddressSigningKeyDeserialisationError signingKeyBs | ||
|
||
-- | Read a file containing a Bech32-encoded Ed25519 BIP32 extended signing | ||
-- key. | ||
readBech32Bip32SigningKeyFile | ||
:: SigningKeyFile | ||
-> IO (Either (FileError CardanoAddressSigningKeyConversionError) Crypto.XPrv) | ||
readBech32Bip32SigningKeyFile (SigningKeyFile fp) = do | ||
eStr <- Exception.try $ readFile fp | ||
case eStr of | ||
Left e -> pure . Left $ FileIOError fp e | ||
Right str -> | ||
case decodeBech32 (Text.concat $ Text.words str) of | ||
Left err -> | ||
pure $ Left $ | ||
FileError fp (CardanoAddressSigningKeyBech32DecodeError err) | ||
Right (_hrPart, _dataPart, bs) -> | ||
pure $ first (FileError fp) (convertBip32SigningKey bs) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I had a go to see what an
The LHS of the |
||
|
||
-- | Read a file containing a Bech32-encoded @cardano-address@ extended | ||
-- signing key. | ||
readSomeCardanoAddressSigningKeyFile | ||
:: CardanoAddressKeyType | ||
-> SigningKeyFile | ||
-> IO (Either (FileError CardanoAddressSigningKeyConversionError) SomeCardanoAddressSigningKey) | ||
readSomeCardanoAddressSigningKeyFile keyType skFile = do | ||
xPrv <- readBech32Bip32SigningKeyFile skFile | ||
pure (toSomeCardanoAddressSigningKey <$> xPrv) | ||
where | ||
toSomeCardanoAddressSigningKey :: Crypto.XPrv -> SomeCardanoAddressSigningKey | ||
toSomeCardanoAddressSigningKey xPrv = | ||
case keyType of | ||
CardanoAddressShelleyPaymentKey -> | ||
ACardanoAddrShelleyPaymentSigningKey | ||
(PaymentExtendedSigningKey xPrv) | ||
CardanoAddressShelleyStakeKey -> | ||
ACardanoAddrShelleyStakeSigningKey (StakeExtendedSigningKey xPrv) | ||
CardanoAddressIcarusPaymentKey -> | ||
ACardanoAddrByronSigningKey $ | ||
ByronSigningKey (Byron.SigningKey xPrv) | ||
CardanoAddressByronPaymentKey -> | ||
ACardanoAddrByronSigningKey $ | ||
ByronSigningKey (Byron.SigningKey xPrv) | ||
|
||
-- | Write a text envelope formatted file containing a @cardano-address@ | ||
-- extended signing key, but converted to a format supported by @cardano-cli@. | ||
writeSomeCardanoAddressSigningKeyFile | ||
:: FilePath | ||
-> SomeCardanoAddressSigningKey | ||
-> IO (Either (FileError ()) ()) | ||
writeSomeCardanoAddressSigningKeyFile outFile skey = | ||
case skey of | ||
ACardanoAddrShelleyPaymentSigningKey sk -> | ||
writeFileTextEnvelope outFile Nothing sk | ||
ACardanoAddrShelleyStakeSigningKey sk -> | ||
writeFileTextEnvelope outFile Nothing sk | ||
ACardanoAddrByronSigningKey sk -> | ||
writeFileTextEnvelope outFile Nothing sk |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps I should use an option parser whose help text specifically denotes that we expect a file containing a Bech32-encoded extended signing key.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems a good idea.