@@ -73,11 +73,13 @@ module Cardano.CLI
7373 , CliKeyScheme (.. )
7474 , DerivationIndex (.. )
7575 , DerivationPath (.. )
76+ , XPrvOrXPub (.. )
7677
7778 , newCliKeyScheme
78- , xPrvToTextTransform
79+ , keyHexCodec
7980 , hoistKeyScheme
8081 , mapKey
82+ , firstHardenedIndex
8183
8284
8385 -- * Logging
@@ -167,6 +169,7 @@ import Cardano.Wallet.Primitive.AddressDerivation
167169 , SomeMnemonic (.. )
168170 , WalletKey (.. )
169171 , XPrv
172+ , XPub
170173 , deriveRewardAccount
171174 , hex
172175 , unXPrv
@@ -197,8 +200,6 @@ import Data.Bifunctor
197200 ( bimap )
198201import Data.ByteArray.Encoding
199202 ( Base (Base16 ), convertFromBase )
200- import Data.ByteString
201- ( ByteString )
202203import Data.Char
203204 ( toLower )
204205import Data.List.Extra
@@ -390,8 +391,12 @@ data CliKeyScheme key m = CliKeyScheme
390391 { allowedWordLengths :: [Int ]
391392 , mnemonicToRootKey :: [Text ] -> m key
392393 , deriveChildKey :: key -> DerivationIndex -> m key
394+ , toPublic :: key -> m key
395+ , inspect :: key -> m Text
393396 }
394397
398+ data XPrvOrXPub = AXPrv XPrv | AXPub XPub
399+
395400-- | Change the underlying monad of a @CliKeyScheme@.
396401hoistKeyScheme
397402 :: (forall a . m1 a -> m2 a )
@@ -401,27 +406,38 @@ hoistKeyScheme eta s = CliKeyScheme
401406 { allowedWordLengths = allowedWordLengths s
402407 , mnemonicToRootKey = eta . mnemonicToRootKey s
403408 , deriveChildKey = \ k i -> eta $ deriveChildKey s k i
409+ , toPublic = eta . toPublic s
410+ , inspect = eta . inspect s
404411 }
405412
406413-- | Pair of functions representing the bidirectional transformation between
407- -- a @XPrv @ and its 96-byte hex-encoded form.
408- xPrvToTextTransform :: (XPrv -> Either String Text , Text -> Either String XPrv )
409- xPrvToTextTransform = (xPrvToHexText, hexTextToXPrv )
414+ -- a @XPrvOrXPub @ and its hex-encoded form.
415+ keyHexCodec :: (XPrvOrXPub -> Either String Text , Text -> Either String XPrvOrXPub )
416+ keyHexCodec = (encode, decode )
410417 where
411- xPrvToHexText :: XPrv -> Either String Text
412- xPrvToHexText =
418+ encode :: XPrvOrXPub -> Either String Text
419+ encode (AXPub xpub) = return . T. pack . B8. unpack . hex . CC. unXPub $ xpub
420+ encode (AXPrv xprv) =
413421 fmap (T. pack . B8. unpack . hex)
414422 . left showErr
415- . unXPrvStripPubCheckRoundtrip
423+ . unXPrvStripPubCheckRoundtrip $ xprv
416424 where
417425 -- NOTE: This error should never happen from using the CLI.
418426 showErr ErrCannotRoundtripToSameXPrv =
419427 " Internal error: Failed to safely encode an extended private key"
420428
421- hexTextToXPrv :: Text -> Either String XPrv
422- hexTextToXPrv txt = do
429+ decode :: Text -> Either String XPrvOrXPub
430+ decode txt = do
423431 bytes <- fromHex $ B8. pack $ T. unpack . T. strip $ txt
424- left showErr $ xPrvFromStrippedPubXPrvCheckRoundtrip bytes
432+ case BS. length bytes of
433+ 96 -> fmap AXPrv $ left showErr $ xPrvFromStrippedPubXPrvCheckRoundtrip bytes
434+ 64 -> fmap AXPub . CC. xpub $ bytes
435+ n -> Left . mconcat $
436+ [ " Expected key to be 96 bytes in the case of a private key"
437+ , " and, 64 bytes for public keys. This key is "
438+ , show n
439+ , " bytes."
440+ ]
425441 where
426442 showErr (ErrInputLengthMismatch expected actual) = mconcat
427443 [ " Expected extended private key to be "
@@ -452,6 +468,8 @@ mapKey (f, g) s = CliKeyScheme
452468 , deriveChildKey = \ k i -> do
453469 k' <- g k
454470 (deriveChildKey s k' i) >>= f
471+ , toPublic = g >=> toPublic s >=> f
472+ , inspect = g >=> inspect s
455473 }
456474
457475eitherToIO :: Either String a -> IO a
@@ -469,7 +487,7 @@ instance ToText DerivationPath where
469487
470488newtype DerivationIndex = DerivationIndex { unDerivationIndex :: Word32 }
471489 deriving (Show , Eq )
472- deriving newtype (Bounded , Enum )
490+ deriving newtype (Bounded , Enum , Ord )
473491
474492firstHardenedIndex :: Word32
475493firstHardenedIndex = getIndex $ minBound @ (Index 'Hardened 'AddressK)
@@ -532,7 +550,7 @@ instance ToText DerivationIndex where
532550 then show (i - firstHardenedIndex) ++ " H"
533551 else show i
534552
535- newCliKeyScheme :: ByronWalletStyle -> CliKeyScheme XPrv (Either String )
553+ newCliKeyScheme :: ByronWalletStyle -> CliKeyScheme XPrvOrXPub (Either String )
536554newCliKeyScheme = \ case
537555 Random ->
538556 -- NOTE
@@ -546,24 +564,30 @@ newCliKeyScheme = \case
546564 in
547565 CliKeyScheme
548566 (apiAllowedLengths proxy)
549- (fmap icarusKeyFromSeed . seedFromMnemonic proxy)
567+ (fmap ( AXPrv . icarusKeyFromSeed) . seedFromMnemonic proxy)
550568 (derive CC. DerivationScheme2 )
569+ (toPub)
570+ insp
551571 Trezor ->
552572 let
553573 proxy = Proxy @ 'API.Trezor
554574 in
555575 CliKeyScheme
556576 (apiAllowedLengths proxy)
557- (fmap icarusKeyFromSeed . seedFromMnemonic proxy)
577+ (fmap ( AXPrv . icarusKeyFromSeed) . seedFromMnemonic proxy)
558578 (derive CC. DerivationScheme2 )
579+ (toPub)
580+ insp
559581 Ledger ->
560582 let
561583 proxy = Proxy @ 'API.Ledger
562584 in
563585 CliKeyScheme
564586 (apiAllowedLengths proxy)
565- (fmap ledgerKeyFromSeed . seedFromMnemonic proxy)
587+ (fmap ( AXPrv . ledgerKeyFromSeed) . seedFromMnemonic proxy)
566588 (derive CC. DerivationScheme2 )
589+ (toPub)
590+ insp
567591 where
568592 seedFromMnemonic
569593 :: forall (s :: ByronWalletStyle ).
@@ -588,18 +612,58 @@ newCliKeyScheme = \case
588612 ledgerKeyFromSeed = Icarus. getKey
589613 . flip Icarus. generateKeyFromHardwareLedger pass
590614
615+ toPub (AXPrv xprv) = return . AXPub . CC. toXPub $ xprv
616+ toPub (AXPub _) = Left " Input is already a public key."
617+
591618 derive
592619 :: CC. DerivationScheme
593- -> XPrv
620+ -> XPrvOrXPub
594621 -> DerivationIndex
595- -> Either String XPrv
596- derive scheme1Or2 k i =
597- return
622+ -> Either String XPrvOrXPub
623+ derive scheme1Or2 ( AXPrv k) i =
624+ return . AXPrv
598625 $ CC. deriveXPrv
599626 scheme1Or2
600627 pass
601628 k
602629 (unDerivationIndex i)
630+ derive scheme1Or2 (AXPub k) i =
631+ maybe (Left err) (return . AXPub )
632+ $ CC. deriveXPub
633+ scheme1Or2
634+ k
635+ (unDerivationIndex i)
636+ where
637+ err = mconcat
638+ [ T. unpack (toText i)
639+ , " is a hardened index. Public key derivation is only possible for"
640+ , " soft indices. \n If the index is correct, please use the"
641+ , " corresponding private key as input."
642+ ]
643+
644+ insp (AXPrv key) = do
645+ let bytes = CC. unXPrv key
646+ let (xprv, rest) = BS. splitAt 64 bytes
647+ let (_pub, cc) = BS. splitAt 32 rest
648+ let encodeToHex = T. pack . B8. unpack . hex
649+ return $ mconcat
650+ [ " extended private key: "
651+ , encodeToHex xprv
652+ , " \n "
653+ , " chain code: "
654+ , encodeToHex cc
655+ ]
656+ insp (AXPub key) = do
657+ let bytes = CC. unXPub key
658+ let (xpub, cc) = BS. splitAt 32 bytes
659+ let encodeToHex = T. pack . B8. unpack . hex
660+ return $ mconcat
661+ [ " extended public key: "
662+ , encodeToHex xpub
663+ , " \n "
664+ , " chain code: "
665+ , encodeToHex cc
666+ ]
603667
604668 -- We don't use passwords to encrypt the keys here.
605669 pass = mempty
@@ -624,7 +688,7 @@ cmdKeyRoot =
624688 scheme :: CliKeyScheme Text IO
625689 scheme =
626690 hoistKeyScheme eitherToIO
627- . mapKey xPrvToTextTransform
691+ . mapKey keyHexCodec
628692 $ newCliKeyScheme keyType
629693
630694data KeyChildArgs = KeyChildArgs
@@ -653,7 +717,7 @@ cmdKeyChild =
653717 scheme :: CliKeyScheme Text IO
654718 scheme =
655719 hoistKeyScheme eitherToIO
656- . mapKey xPrvToTextTransform
720+ . mapKey keyHexCodec
657721 $ newCliKeyScheme Icarus
658722
659723newtype KeyPublicArgs = KeyPublicArgs
@@ -668,11 +732,16 @@ cmdKeyPublic =
668732 cmd = fmap exec $
669733 KeyPublicArgs <$> keyArgument
670734
671- exec (KeyPublicArgs hexPrv) = do
672- let (_, decodePrv) = xPrvToTextTransform
673- let encodePub = T. pack . B8. unpack . hex . CC. unXPub
674- pubHex <- eitherToIO $ encodePub . CC. toXPub <$> decodePrv hexPrv
675- TIO. putStrLn pubHex
735+ exec (KeyPublicArgs key) = do
736+ pub <- toPublic scheme key
737+ TIO. putStrLn pub
738+
739+ scheme :: CliKeyScheme Text IO
740+ scheme =
741+ hoistKeyScheme eitherToIO
742+ . mapKey keyHexCodec
743+ $ newCliKeyScheme Icarus
744+
676745
677746newtype KeyInspectArgs = KeyInspectArgs
678747 { _key :: Text
@@ -686,25 +755,14 @@ cmdKeyInspect =
686755 cmd = fmap exec $
687756 KeyInspectArgs <$> keyArgument
688757
689- exec (KeyInspectArgs hexKey) = do
690- let (_, hex2key) = xPrvToTextTransform
691- res <- eitherToIO $ do
692- -- Extract extended private key and chain code from a @XPrv@
693- keyBytes <- CC. unXPrv <$> hex2key hexKey
694- let (xprv, rest) = BS. splitAt 64 keyBytes
695- let (_pub, cc) = BS. splitAt 32 rest
696- return $ mconcat
697- [ " extended private key: "
698- , toHex xprv
699- , " \n "
700- , " chain code: "
701- , toHex cc
702- ]
703- TIO. putStrLn res
758+ exec (KeyInspectArgs key) =
759+ inspect scheme key >>= TIO. putStrLn
704760
705- where
706- toHex :: ByteString -> Text
707- toHex = T. pack . B8. unpack . hex
761+ scheme :: CliKeyScheme Text IO
762+ scheme =
763+ hoistKeyScheme eitherToIO
764+ . mapKey keyHexCodec
765+ $ newCliKeyScheme Icarus
708766
709767{- ------------------------------------------------------------------------------
710768 Commands - 'mnemonic'
0 commit comments