packages feed

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

{-# 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