{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE LambdaCase #-} {-# 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.FromRecoveryPhrase ( Cmd (..) , mod , run ) where import Prelude hiding ( mod ) import Cardano.Address.Derivation ( xprvToBytes ) import Codec.Binary.Bech32 ( HumanReadablePart ) import Codec.Binary.Encoding ( AbstractEncoding (..) ) import Data.Text ( Text ) import Options.Applicative ( CommandFields , Mod , command , footerDoc , helper , info , optional , progDesc ) import Options.Applicative.Help.Pretty ( Doc, annotate, bold, indent, pretty, vsep ) import Options.Applicative.Style ( Passphrase (..) , PassphraseInfo (..) , PassphraseInput (..) , PassphraseInputMode , Style (..) , fileOpt , generateRootKey , passphraseInfoOpt , passphraseInputModeOpt , styleArg ) import System.IO ( stderr, stdin, stdout ) import System.IO.Extra ( hGetPassphraseBytes , hGetPassphraseMnemonic , hGetSomeMnemonic , hGetSomeMnemonicInteractively , hPutBytes , progName ) import qualified Cardano.Codec.Bech32.Prefixes as CIP5 data Cmd = FromRecoveryPhrase { style :: Style , passphraseInfo :: Maybe PassphraseInfo , passphraseInputMode :: PassphraseInputMode , passphraseFromFile :: Maybe FilePath } deriving (Show) mod :: (Cmd -> parent) -> Mod CommandFields parent mod liftCmd = command "from-recovery-phrase" $ info (helper <*> fmap liftCmd parser) $ mempty <> progDesc "Convert a recovery phrase to an extended private key" <> footerDoc (Just $ vsep [ prettyText "The recovery phrase without passphrase is read from stdin." , prettyText "" , prettyText "Example:" , indent 2 $ annotate bold $ pretty $ "$ "<>progName<>" recovery-phrase generate \\" , indent 2 $ annotate bold $ pretty $ "| "<>progName<>" key from-recovery-phrase Icarus" , prettyText "" , prettyText "The recovery phrase with passphrase can be entered interactively or from file." , prettyText "In both cases passhrase can take form of mnemonic, base16, base64, utf8 or octet array." , prettyText "In interactive case one can select explicit, sensitive or silent mode." , prettyText "" , prettyText "Example:" , indent 2 $ annotate bold $ pretty $ "$ "<>progName<>" key from-recovery-phrase Shelley --passphrase from-mnemonic --sensitive" , indent 2 $ annotate bold $ prettyText "Please enter a [9, 12, 15, 18, 21, 24] word mnemonic:" , indent 2 $ annotate bold $ prettyText "**********************************************************************************************************" , indent 2 $ annotate bold $ prettyText "Please enter a 9–12 word second factor:" , indent 2 $ annotate bold $ prettyText "*************************************************************" , prettyText "" , prettyText "In case of passphrase reading from file the recovery phrase is read from stdin." , prettyText "" , prettyText "Example:" , indent 2 $ annotate bold $ prettyText "$ echo \"Secret Secondary Phrase\" > sndfactor.prv" , indent 2 $ annotate bold $ pretty $ "$ "<>progName<>" recovery-phrase generate \\" , indent 2 $ annotate bold $ pretty $ "| "<>progName<>" key from-recovery-phrase Shelley --from-file \"./sndfactor.prv\"" , prettyText "" , indent 2 $ annotate bold $ pretty $ "$ "<>progName<>" recovery-phrase generate --size 12 > sndfactor.prv" , indent 2 $ annotate bold $ pretty $ "$ "<>progName<>" recovery-phrase generate \\" , indent 2 $ annotate bold $ pretty $ "| "<>progName<>" key from-recovery-phrase Shelley --passphrase from-mnemonic --from-file \"./sndfactor.prv\"" ]) where parser = FromRecoveryPhrase <$> styleArg <*> optional passphraseInfoOpt <*> passphraseInputModeOpt <*> optional fileOpt prettyText :: Text -> Doc prettyText = pretty run :: Cmd -> IO () run FromRecoveryPhrase{style,passphraseInfo, passphraseInputMode,passphraseFromFile} = do (someMnemonic, passphrase) <- case passphraseInfo of Nothing -> do mnemonic <- hGetSomeMnemonic stdin pure (mnemonic, Nothing) Just pinfo -> do mnemonic <- case passphraseFromFile of Nothing -> do let prompt = "Please enter a [9, 12, 15, 18, 21, 24] word mnemonic:" hGetSomeMnemonicInteractively (stdin, stderr) passphraseInputMode prompt Just _ -> hGetSomeMnemonic stdin let passphraseSrc = maybe Interactive FromFile passphraseFromFile passwd <- handlePassphraseInfo passphraseSrc pinfo pure (mnemonic, Just passwd) rootK <- generateRootKey someMnemonic passphrase style hPutBytes stdout (xprvToBytes rootK) (EBech32 $ styleHrp style) where handlePassphraseInfo passphraseSrc = \case Mnemonic -> do let prompt = "Please enter a 9–12 word second factor:" p <- hGetPassphraseMnemonic (stdin, stderr) passphraseInputMode passphraseSrc prompt pure $ FromMnemonic p Hex -> do let prompt = "Please enter hex-encoded passphrase:" p <- hGetPassphraseBytes (stdin, stderr) passphraseInputMode passphraseSrc prompt Hex pure $ FromEncoded p Base64 -> do let prompt = "Please enter base64-encoded passphrase:" p <- hGetPassphraseBytes (stdin, stderr) passphraseInputMode passphraseSrc prompt Base64 pure $ FromEncoded p Utf8 -> do let prompt = "Please enter utf8-encoded passphrase:" p <- hGetPassphraseBytes (stdin, stderr) passphraseInputMode passphraseSrc prompt Utf8 pure $ FromEncoded p Octets -> do let prompt = "Please enter passphrase in the form of octet array:" p <- hGetPassphraseBytes (stdin, stderr) passphraseInputMode passphraseSrc prompt Octets pure $ FromEncoded p styleHrp :: Style -> HumanReadablePart styleHrp Shared = CIP5.root_shared_xsk styleHrp _ = CIP5.root_xsk