From f63761ebe5a9edcbf01310f7ebf263f7a5f0e12b Mon Sep 17 00:00:00 2001 From: KtorZ Date: Tue, 17 Mar 2020 13:20:33 +0100 Subject: [PATCH 01/11] implement export-wallets for Byron reboot migration This little command-line tool extract the name and encrypted XPrv of known wallets and spits them back in a user-friendly format on stdout. $ export-wallets --mainnet --wallet-db-path state-wallet-mainnet/wallet-db --keyfile state-wallet-mainnet/secret.key [ { "encrypted_root_private_key": "5da58210e3f6e2b1e24659158554f91bb5970202ba0e0cd2d32767ce3c2893c29515a5469493f6133857c0dc86f127aa70e46d047424e07f7221715357bfb35052aed22cefd53ea7d75b5804add54e653c21e03ffd7c01cb8f6c033f7d383cc127046148d89a1828b76bdb46b56f6fec06d31b5b412dc68ab4b5ffa2418e7e8d", "name": "My Wallet", "passphrase_hash": "31347c387c317c57434478796c7064635a736853367a6d3150766d54456a34667038795a34623145557868627765347a36596645413d3d7c48544739445a456d4f2b79535a6a705a76553145614d795a437170732b43347330704738414562745638773d" } ] The format matches exactly the JSON format expected by the cardano-wallet for restoring legacy wallet from xprv. --- wallet/cardano-wallet.cabal | 43 +++++++++++ wallet/export-wallets/Main.hs | 135 ++++++++++++++++++++++++++++++++++ 2 files changed, 178 insertions(+) create mode 100644 wallet/export-wallets/Main.hs diff --git a/wallet/cardano-wallet.cabal b/wallet/cardano-wallet.cabal index 106de7d5ea2..a7ee1871ea1 100644 --- a/wallet/cardano-wallet.cabal +++ b/wallet/cardano-wallet.cabal @@ -337,6 +337,49 @@ executable cardano-generate-swagger-file main-is: Main.hs +executable export-wallets + default-language: + Haskell2010 + default-extensions: + FlexibleContexts + MonadFailDesugaring + NoImplicitPrelude + OverloadedStrings + RecordWildCards + ScopedTypeVariables + TypeApplications + TypeOperators + ghc-options: + -threaded -rtsopts + -Wall + -fno-warn-orphans + -O2 + + build-depends: + base + , aeson + , aeson-pretty + , bytestring + , cardano-crypto + , cardano-sl + , cardano-sl-core + , cardano-sl-crypto + , cardano-sl-crypto + , cardano-sl-infra + , cardano-sl-util + , cardano-wallet + , contravariant + , memory + , optparse-applicative + , text + , universum >= 0.1.11 + + hs-source-dirs: + export-wallets + main-is: + Main.hs + + test-suite unit default-language: Haskell2010 diff --git a/wallet/export-wallets/Main.hs b/wallet/export-wallets/Main.hs new file mode 100644 index 00000000000..d6410aea748 --- /dev/null +++ b/wallet/export-wallets/Main.hs @@ -0,0 +1,135 @@ +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE NamedFieldPuns #-} +{-# LANGUAGE TypeApplications #-} + +module Main where + +import Universum + +import Cardano.Crypto.Wallet (unXPrv) +import Cardano.Wallet.Kernel (DatabaseMode (..), DatabaseOptions (..), + bracketPassiveWallet) +import Cardano.Wallet.Kernel.DB.HdWallet (HdRoot (..), HdRootId (..), + HdRootId (..), WalletName (..), eskToHdRootId) +import Cardano.Wallet.Kernel.DB.InDb (fromDb) +import Cardano.Wallet.Kernel.Keystore (bracketLegacyKeystore) +import Cardano.Wallet.Kernel.NodeStateAdaptor (mockNodeStateDef) +import Data.Aeson (ToJSON (..), (.=)) +import Data.ByteArray.Encoding (Base (..), convertToBase) +import Data.ByteString (ByteString) +import Data.Functor.Contravariant (Contravariant (..), Op (..)) +import Options.Applicative +import Pos.Core.Common (addrToBase58) +import Pos.Core.NetworkMagic (makeNetworkMagic) +import Pos.Crypto (EncryptedPass (..), EncryptedSecretKey (..)) +import Pos.Crypto.Configuration (ProtocolMagic (..), + ProtocolMagicId (..), RequiresNetworkMagic (..)) +import Pos.Infra.InjectFail (mkFInjects) +import Pos.Util.Log.Severity (Severity (..)) +import Pos.Util.Trace (Trace (..)) +import Pos.Util.UserSecret (readUserSecret) + +import qualified Cardano.Wallet.Kernel as Kernel +import qualified Cardano.Wallet.Kernel.Internal as Kernel +import qualified Cardano.Wallet.Kernel.Keystore as Keystore +import qualified Cardano.Wallet.Kernel.Read as Kernel +import qualified Data.Aeson as Json +import qualified Data.Aeson.Encode.Pretty as Json +import qualified Data.ByteString.Char8 as B8 +import qualified Data.ByteString.Lazy.Char8 as BL8 +import qualified Data.Text.Encoding as T +import qualified Data.Text.IO as TIO + +data Options = Options + { pm :: ProtocolMagic + , dbPath :: FilePath + , usPath :: FilePath + } + +main :: IO () +main = do + let preferences = prefs showHelpOnEmpty + Options{pm,dbPath,usPath} <- customExecParser preferences parserInfo + userSecret <- readUserSecret (contramap snd stderrTrace) usPath + bracketLegacyKeystore userSecret $ \ks -> do + let dbMode = UseFilePath $ DatabaseOptions + { Kernel.dbPathAcidState = dbPath <> "-acid" + , Kernel.dbPathMetadata = dbPath <> "-sqlite.sqlite3" + , Kernel.dbRebuild = False + } + fInjects <- mkFInjects Nothing + bracketPassiveWallet pm dbMode log ks mockNodeStateDef fInjects $ \pw -> do + wallets <- extractWallet pw + BL8.putStrLn $ Json.encodePretty (Export <$> wallets) + where + log = const (B8.hPutStrLn stderr . T.encodeUtf8) + stderrTrace = Trace $ Op $ TIO.hPutStrLn stderr + +extractWallet + :: Kernel.PassiveWallet + -- ^ A passive wallet + -> IO [(WalletName, EncryptedSecretKey)] +extractWallet pw = do + wKeys <- Keystore.getKeys (pw ^. Kernel.walletKeystore) + let nm = makeNetworkMagic (pw ^. Kernel.walletProtocolMagic) + snapshot <- Kernel.getWalletSnapshot pw + fmap catMaybes $ forM wKeys $ \esk -> do + let rootId = eskToHdRootId nm esk + case Kernel.lookupHdRootId snapshot rootId of + Left _ -> do + let wid = T.decodeUtf8 $ addrToBase58 $ getHdRootId rootId ^. fromDb + log Error $ "No wallet for id: " <> wid + pure Nothing + + Right HdRoot{_hdRootName} -> + pure $ Just (_hdRootName, esk) + where + log = pw ^. Kernel.walletLogMessage + +newtype Export a = Export a deriving (Show) + +instance ToJSON (Export (WalletName, EncryptedSecretKey)) where + toJSON (Export (name, EncryptedSecretKey{eskPayload, eskHash})) = Json.object + [ "name" .= getWalletName name + , "encrypted_root_private_key" .= base16 (unXPrv eskPayload) + , "passphrase_hash" .= base16 (getEncryptedPass eskHash) + ] + where + base16 = T.decodeUtf8 . convertToBase @ByteString @ByteString Base16 + +-- +-- Command-line +-- +-- + +parserInfo :: ParserInfo Options +parserInfo = info (helper <*> parser) $ + progDesc "Export known legacy wallets with their encrypted secret key" + where + parser = Options <$> pmOption <*> dbOption <*> usOption + +-- --mainnet | --testnet MAGIC +pmOption :: Parser ProtocolMagic +pmOption = mainnetFlag <|> (ProtocolMagic <$> magicOption <*> pure RequiresMagic) + where + mainnetFlag = flag' + (ProtocolMagic (ProtocolMagicId 764824073) RequiresNoMagic) + (long "mainnet") + + magicOption = fmap ProtocolMagicId $ option auto $ mempty + <> long "testnet" + <> metavar "MAGIC" + +-- --db-path FILEPATH +dbOption :: Parser FilePath +dbOption = option str $ mempty + <> long "wallet-db-path" + <> metavar "FILEPATH" + <> help "Path to the wallet's database." + +-- --keyfile FILEPATH +usOption :: Parser FilePath +usOption = option str $ mempty + <> long "keyfile" + <> metavar "FILEPATH" + <> help "Path to the secret key-store." From 148076e40c6393c022b0d60e9fa0c0bd50d07fab Mon Sep 17 00:00:00 2001 From: KtorZ Date: Tue, 17 Mar 2020 13:28:21 +0100 Subject: [PATCH 02/11] Return 'null' for 'passphrase_hash' when there's no passphrase set $ export-wallets --mainnet --wallet-db-path state-wallet-mainnet/wallet-db --keyfile state-wallet-mainnet/secret.key [ { "encrypted_root_private_key": "b824f5268bdf05d783b0c594936e51c65d6d155021ddcbdb3370bb7a51caba4bedece046fc9d12143ce900d8238e444904e08a85af80299b012e7f12030a2ab6d3d1cf6fc565476c17326baebddcf31c5e3409ba697b9bf04ac588cb59868c22d043e0237a3ba2eb167d1504dd93f788fc4a72503a261cf338ad7e4cf8837aff", "name": "My Wallet", "passphrase_hash": null }, { "encrypted_root_private_key": "5da58210e3f6e2b1e24659158554f91bb5970202ba0e0cd2d32767ce3c2893c29515a5469493f6133857c0dc86f127aa70e46d047424e07f7221715357bfb35052aed22cefd53ea7d75b5804add54e653c21e03ffd7c01cb8f6c033f7d383cc127046148d89a1828b76bdb46b56f6fec06d31b5b412dc68ab4b5ffa2418e7e8d", "name": "My Wallet", "passphrase_hash": "31347c387c317c57434478796c7064635a736853367a6d3150766d54456a34667038795a34623145557868627765347a36596645413d3d7c48544739445a456d4f2b79535a6a705a76553145614d795a437170732b43347330704738414562745638773d" } ] --- wallet/export-wallets/Main.hs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/wallet/export-wallets/Main.hs b/wallet/export-wallets/Main.hs index d6410aea748..ca3acff5e1f 100644 --- a/wallet/export-wallets/Main.hs +++ b/wallet/export-wallets/Main.hs @@ -24,6 +24,7 @@ import Pos.Core.NetworkMagic (makeNetworkMagic) import Pos.Crypto (EncryptedPass (..), EncryptedSecretKey (..)) import Pos.Crypto.Configuration (ProtocolMagic (..), ProtocolMagicId (..), RequiresNetworkMagic (..)) +import Pos.Crypto.Signing (checkPassMatches, emptyPassphrase) import Pos.Infra.InjectFail (mkFInjects) import Pos.Util.Log.Severity (Severity (..)) import Pos.Util.Trace (Trace (..)) @@ -67,7 +68,6 @@ main = do extractWallet :: Kernel.PassiveWallet - -- ^ A passive wallet -> IO [(WalletName, EncryptedSecretKey)] extractWallet pw = do wKeys <- Keystore.getKeys (pw ^. Kernel.walletKeystore) @@ -89,10 +89,12 @@ extractWallet pw = do newtype Export a = Export a deriving (Show) instance ToJSON (Export (WalletName, EncryptedSecretKey)) where - toJSON (Export (name, EncryptedSecretKey{eskPayload, eskHash})) = Json.object + toJSON (Export (name, esk@EncryptedSecretKey{eskPayload, eskHash})) = Json.object [ "name" .= getWalletName name , "encrypted_root_private_key" .= base16 (unXPrv eskPayload) - , "passphrase_hash" .= base16 (getEncryptedPass eskHash) + , "passphrase_hash" .= case (checkPassMatches emptyPassphrase esk) of + Nothing -> toJSON $ base16 (getEncryptedPass eskHash) + Just () -> Json.Null ] where base16 = T.decodeUtf8 . convertToBase @ByteString @ByteString Base16 From 787e72e93c90a97f2b3e470c84abe0f91402319f Mon Sep 17 00:00:00 2001 From: Michael Bishop Date: Tue, 17 Mar 2020 09:39:09 -0300 Subject: [PATCH 03/11] regen nix files --- nix/.stack.nix/cardano-wallet.nix | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/nix/.stack.nix/cardano-wallet.nix b/nix/.stack.nix/cardano-wallet.nix index b40d0011f64..c2f42f7ffe7 100644 --- a/nix/.stack.nix/cardano-wallet.nix +++ b/nix/.stack.nix/cardano-wallet.nix @@ -134,6 +134,27 @@ (hsPkgs.universum) ]; }; + "export-wallets" = { + depends = [ + (hsPkgs.base) + (hsPkgs.aeson) + (hsPkgs.aeson-pretty) + (hsPkgs.bytestring) + (hsPkgs.cardano-crypto) + (hsPkgs.cardano-sl) + (hsPkgs.cardano-sl-core) + (hsPkgs.cardano-sl-crypto) + (hsPkgs.cardano-sl-crypto) + (hsPkgs.cardano-sl-infra) + (hsPkgs.cardano-sl-util) + (hsPkgs.cardano-wallet) + (hsPkgs.contravariant) + (hsPkgs.memory) + (hsPkgs.optparse-applicative) + (hsPkgs.text) + (hsPkgs.universum) + ]; + }; }; tests = { "unit" = { From da6645c8aba62c19eda55459606ebbeb4c1549b7 Mon Sep 17 00:00:00 2001 From: KtorZ Date: Wed, 18 Mar 2020 08:46:52 +0100 Subject: [PATCH 04/11] fail when given a non-existing database path Before that, the script would simply create a new empty database and pretty much fail to export every wallet in the keystore --- wallet/cardano-wallet.cabal | 1 + wallet/export-wallets/Main.hs | 18 ++++++++++++------ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/wallet/cardano-wallet.cabal b/wallet/cardano-wallet.cabal index a7ee1871ea1..b903b9326d6 100644 --- a/wallet/cardano-wallet.cabal +++ b/wallet/cardano-wallet.cabal @@ -369,6 +369,7 @@ executable export-wallets , cardano-sl-util , cardano-wallet , contravariant + , directory , memory , optparse-applicative , text diff --git a/wallet/export-wallets/Main.hs b/wallet/export-wallets/Main.hs index ca3acff5e1f..83da74362ca 100644 --- a/wallet/export-wallets/Main.hs +++ b/wallet/export-wallets/Main.hs @@ -1,4 +1,5 @@ {-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE LambdaCase #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE TypeApplications #-} @@ -29,6 +30,7 @@ import Pos.Infra.InjectFail (mkFInjects) import Pos.Util.Log.Severity (Severity (..)) import Pos.Util.Trace (Trace (..)) import Pos.Util.UserSecret (readUserSecret) +import System.Directory (doesDirectoryExist) import qualified Cardano.Wallet.Kernel as Kernel import qualified Cardano.Wallet.Kernel.Internal as Kernel @@ -51,20 +53,24 @@ main :: IO () main = do let preferences = prefs showHelpOnEmpty Options{pm,dbPath,usPath} <- customExecParser preferences parserInfo + let dbOpts = DatabaseOptions + { dbPathAcidState = dbPath <> "-acid" + , dbPathMetadata = dbPath <> "-sqlite.sqlite3" + , dbRebuild = False + } + guardDatabase dbOpts userSecret <- readUserSecret (contramap snd stderrTrace) usPath bracketLegacyKeystore userSecret $ \ks -> do - let dbMode = UseFilePath $ DatabaseOptions - { Kernel.dbPathAcidState = dbPath <> "-acid" - , Kernel.dbPathMetadata = dbPath <> "-sqlite.sqlite3" - , Kernel.dbRebuild = False - } fInjects <- mkFInjects Nothing - bracketPassiveWallet pm dbMode log ks mockNodeStateDef fInjects $ \pw -> do + bracketPassiveWallet pm (UseFilePath dbOpts) log ks mockNodeStateDef fInjects $ \pw -> do wallets <- extractWallet pw BL8.putStrLn $ Json.encodePretty (Export <$> wallets) where log = const (B8.hPutStrLn stderr . T.encodeUtf8) stderrTrace = Trace $ Op $ TIO.hPutStrLn stderr + guardDatabase = doesDirectoryExist . dbPathAcidState >=> \case + True -> pure () + False -> fail "There's no acid-state database matching the given path." extractWallet :: Kernel.PassiveWallet From 9b53dc14cddebaef94dfcd7bdddd39ef6d9f1076 Mon Sep 17 00:00:00 2001 From: KtorZ Date: Wed, 18 Mar 2020 10:49:12 +0100 Subject: [PATCH 05/11] re-generate nix machinery --- nix/.stack.nix/cardano-wallet.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/nix/.stack.nix/cardano-wallet.nix b/nix/.stack.nix/cardano-wallet.nix index c2f42f7ffe7..9aac56cd455 100644 --- a/nix/.stack.nix/cardano-wallet.nix +++ b/nix/.stack.nix/cardano-wallet.nix @@ -149,6 +149,7 @@ (hsPkgs.cardano-sl-util) (hsPkgs.cardano-wallet) (hsPkgs.contravariant) + (hsPkgs.directory) (hsPkgs.memory) (hsPkgs.optparse-applicative) (hsPkgs.text) From 1702a9eb12953e76a5a8b070e5c6a2bfb02ff160 Mon Sep 17 00:00:00 2001 From: KtorZ Date: Fri, 27 Mar 2020 20:47:08 +0100 Subject: [PATCH 06/11] actually returns corresponding scrypt hash even for empty passphrases --- wallet/export-wallets/Main.hs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/wallet/export-wallets/Main.hs b/wallet/export-wallets/Main.hs index 83da74362ca..c1102678e0f 100644 --- a/wallet/export-wallets/Main.hs +++ b/wallet/export-wallets/Main.hs @@ -25,7 +25,6 @@ import Pos.Core.NetworkMagic (makeNetworkMagic) import Pos.Crypto (EncryptedPass (..), EncryptedSecretKey (..)) import Pos.Crypto.Configuration (ProtocolMagic (..), ProtocolMagicId (..), RequiresNetworkMagic (..)) -import Pos.Crypto.Signing (checkPassMatches, emptyPassphrase) import Pos.Infra.InjectFail (mkFInjects) import Pos.Util.Log.Severity (Severity (..)) import Pos.Util.Trace (Trace (..)) @@ -95,12 +94,10 @@ extractWallet pw = do newtype Export a = Export a deriving (Show) instance ToJSON (Export (WalletName, EncryptedSecretKey)) where - toJSON (Export (name, esk@EncryptedSecretKey{eskPayload, eskHash})) = Json.object + toJSON (Export (name, EncryptedSecretKey{eskPayload, eskHash})) = Json.object [ "name" .= getWalletName name , "encrypted_root_private_key" .= base16 (unXPrv eskPayload) - , "passphrase_hash" .= case (checkPassMatches emptyPassphrase esk) of - Nothing -> toJSON $ base16 (getEncryptedPass eskHash) - Just () -> Json.Null + , "passphrase_hash" .= base16 (getEncryptedPass eskHash) ] where base16 = T.decodeUtf8 . convertToBase @ByteString @ByteString Base16 From 776a6384c69e1033a07eaa7532413cc78d024c6d Mon Sep 17 00:00:00 2001 From: KtorZ Date: Wed, 8 Apr 2020 16:29:54 +0200 Subject: [PATCH 07/11] returns wallets with no metadata if prv key exists Name is therefore set to null. --- wallet/export-wallets/Main.hs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/wallet/export-wallets/Main.hs b/wallet/export-wallets/Main.hs index c1102678e0f..9cbade0f9be 100644 --- a/wallet/export-wallets/Main.hs +++ b/wallet/export-wallets/Main.hs @@ -73,29 +73,26 @@ main = do extractWallet :: Kernel.PassiveWallet - -> IO [(WalletName, EncryptedSecretKey)] + -> IO [(Maybe WalletName, EncryptedSecretKey)] extractWallet pw = do wKeys <- Keystore.getKeys (pw ^. Kernel.walletKeystore) let nm = makeNetworkMagic (pw ^. Kernel.walletProtocolMagic) snapshot <- Kernel.getWalletSnapshot pw - fmap catMaybes $ forM wKeys $ \esk -> do + forM wKeys $ \esk -> do let rootId = eskToHdRootId nm esk case Kernel.lookupHdRootId snapshot rootId of - Left _ -> do - let wid = T.decodeUtf8 $ addrToBase58 $ getHdRootId rootId ^. fromDb - log Error $ "No wallet for id: " <> wid - pure Nothing - + Left _ -> + pure (Nothing, esk) Right HdRoot{_hdRootName} -> - pure $ Just (_hdRootName, esk) + pure (Just _hdRootName, esk) where log = pw ^. Kernel.walletLogMessage newtype Export a = Export a deriving (Show) -instance ToJSON (Export (WalletName, EncryptedSecretKey)) where +instance ToJSON (Export (Maybe WalletName, EncryptedSecretKey)) where toJSON (Export (name, EncryptedSecretKey{eskPayload, eskHash})) = Json.object - [ "name" .= getWalletName name + [ "name" .= getWalletName <$> name , "encrypted_root_private_key" .= base16 (unXPrv eskPayload) , "passphrase_hash" .= base16 (getEncryptedPass eskHash) ] From 996a17fb3e1cc186f9454e0828f7ef2a7e17d79c Mon Sep 17 00:00:00 2001 From: KtorZ Date: Wed, 8 Apr 2020 16:30:59 +0200 Subject: [PATCH 08/11] add wallet id (new format) to the exported JSON --- wallet/cardano-wallet.cabal | 1 + wallet/export-wallets/Main.hs | 17 ++++++++--------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/wallet/cardano-wallet.cabal b/wallet/cardano-wallet.cabal index b903b9326d6..988be71f3c3 100644 --- a/wallet/cardano-wallet.cabal +++ b/wallet/cardano-wallet.cabal @@ -369,6 +369,7 @@ executable export-wallets , cardano-sl-util , cardano-wallet , contravariant + , cryptonite , directory , memory , optparse-applicative diff --git a/wallet/export-wallets/Main.hs b/wallet/export-wallets/Main.hs index 9cbade0f9be..f21da592ba5 100644 --- a/wallet/export-wallets/Main.hs +++ b/wallet/export-wallets/Main.hs @@ -7,26 +7,24 @@ module Main where import Universum -import Cardano.Crypto.Wallet (unXPrv) +import Cardano.Crypto.Wallet (toXPub, unXPrv, unXPub) import Cardano.Wallet.Kernel (DatabaseMode (..), DatabaseOptions (..), bracketPassiveWallet) -import Cardano.Wallet.Kernel.DB.HdWallet (HdRoot (..), HdRootId (..), - HdRootId (..), WalletName (..), eskToHdRootId) -import Cardano.Wallet.Kernel.DB.InDb (fromDb) +import Cardano.Wallet.Kernel.DB.HdWallet (HdRoot (..), + WalletName (..), eskToHdRootId) import Cardano.Wallet.Kernel.Keystore (bracketLegacyKeystore) import Cardano.Wallet.Kernel.NodeStateAdaptor (mockNodeStateDef) +import Crypto.Hash (Blake2b_160, hash) import Data.Aeson (ToJSON (..), (.=)) import Data.ByteArray.Encoding (Base (..), convertToBase) import Data.ByteString (ByteString) import Data.Functor.Contravariant (Contravariant (..), Op (..)) import Options.Applicative -import Pos.Core.Common (addrToBase58) import Pos.Core.NetworkMagic (makeNetworkMagic) import Pos.Crypto (EncryptedPass (..), EncryptedSecretKey (..)) import Pos.Crypto.Configuration (ProtocolMagic (..), ProtocolMagicId (..), RequiresNetworkMagic (..)) import Pos.Infra.InjectFail (mkFInjects) -import Pos.Util.Log.Severity (Severity (..)) import Pos.Util.Trace (Trace (..)) import Pos.Util.UserSecret (readUserSecret) import System.Directory (doesDirectoryExist) @@ -37,6 +35,7 @@ import qualified Cardano.Wallet.Kernel.Keystore as Keystore import qualified Cardano.Wallet.Kernel.Read as Kernel import qualified Data.Aeson as Json import qualified Data.Aeson.Encode.Pretty as Json +import qualified Data.ByteArray as BA import qualified Data.ByteString.Char8 as B8 import qualified Data.ByteString.Lazy.Char8 as BL8 import qualified Data.Text.Encoding as T @@ -85,19 +84,19 @@ extractWallet pw = do pure (Nothing, esk) Right HdRoot{_hdRootName} -> pure (Just _hdRootName, esk) - where - log = pw ^. Kernel.walletLogMessage newtype Export a = Export a deriving (Show) instance ToJSON (Export (Maybe WalletName, EncryptedSecretKey)) where toJSON (Export (name, EncryptedSecretKey{eskPayload, eskHash})) = Json.object - [ "name" .= getWalletName <$> name + [ "id" .= base16 (mkWalletId eskPayload) + , "name" .= (getWalletName <$> name) , "encrypted_root_private_key" .= base16 (unXPrv eskPayload) , "passphrase_hash" .= base16 (getEncryptedPass eskHash) ] where base16 = T.decodeUtf8 . convertToBase @ByteString @ByteString Base16 + mkWalletId = BA.convert . hash @_ @Blake2b_160 . unXPub . toXPub -- -- Command-line From 6054e7e8a7e6dcce29aed88fcd1c131206744d24 Mon Sep 17 00:00:00 2001 From: KtorZ Date: Wed, 8 Apr 2020 17:19:56 +0200 Subject: [PATCH 09/11] re-generate nix machinery --- nix/.stack.nix/cardano-wallet.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/nix/.stack.nix/cardano-wallet.nix b/nix/.stack.nix/cardano-wallet.nix index 9aac56cd455..03024756f23 100644 --- a/nix/.stack.nix/cardano-wallet.nix +++ b/nix/.stack.nix/cardano-wallet.nix @@ -149,6 +149,7 @@ (hsPkgs.cardano-sl-util) (hsPkgs.cardano-wallet) (hsPkgs.contravariant) + (hsPkgs.cryptonite) (hsPkgs.directory) (hsPkgs.memory) (hsPkgs.optparse-applicative) From 13f5729585e298643ecfc757fbb2da125a90e057 Mon Sep 17 00:00:00 2001 From: KtorZ Date: Wed, 15 Apr 2020 14:24:24 +0200 Subject: [PATCH 10/11] make acid-state database optional from the command-line --- wallet/export-wallets/Main.hs | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/wallet/export-wallets/Main.hs b/wallet/export-wallets/Main.hs index f21da592ba5..01ddb020a37 100644 --- a/wallet/export-wallets/Main.hs +++ b/wallet/export-wallets/Main.hs @@ -43,7 +43,7 @@ import qualified Data.Text.IO as TIO data Options = Options { pm :: ProtocolMagic - , dbPath :: FilePath + , dbPath :: Maybe FilePath , usPath :: FilePath } @@ -51,25 +51,34 @@ main :: IO () main = do let preferences = prefs showHelpOnEmpty Options{pm,dbPath,usPath} <- customExecParser preferences parserInfo - let dbOpts = DatabaseOptions - { dbPathAcidState = dbPath <> "-acid" - , dbPathMetadata = dbPath <> "-sqlite.sqlite3" - , dbRebuild = False - } - guardDatabase dbOpts + dbMode <- getDbMode dbPath userSecret <- readUserSecret (contramap snd stderrTrace) usPath bracketLegacyKeystore userSecret $ \ks -> do fInjects <- mkFInjects Nothing - bracketPassiveWallet pm (UseFilePath dbOpts) log ks mockNodeStateDef fInjects $ \pw -> do + bracketPassiveWallet pm dbMode log ks mockNodeStateDef fInjects $ \pw -> do wallets <- extractWallet pw BL8.putStrLn $ Json.encodePretty (Export <$> wallets) where log = const (B8.hPutStrLn stderr . T.encodeUtf8) + stderrTrace = Trace $ Op $ TIO.hPutStrLn stderr + guardDatabase = doesDirectoryExist . dbPathAcidState >=> \case True -> pure () False -> fail "There's no acid-state database matching the given path." + getDbMode = \case + Nothing -> + pure UseInMemory + Just dbPath -> do + let dbOpts = DatabaseOptions + { dbPathAcidState = dbPath <> "-acid" + , dbPathMetadata = dbPath <> "-sqlite.sqlite3" + , dbRebuild = False + } + guardDatabase dbOpts + pure (UseFilePath dbOpts) + extractWallet :: Kernel.PassiveWallet -> IO [(Maybe WalletName, EncryptedSecretKey)] @@ -122,8 +131,8 @@ pmOption = mainnetFlag <|> (ProtocolMagic <$> magicOption <*> pure RequiresMagic <> metavar "MAGIC" -- --db-path FILEPATH -dbOption :: Parser FilePath -dbOption = option str $ mempty +dbOption :: Parser (Maybe FilePath) +dbOption = optional $ option str $ mempty <> long "wallet-db-path" <> metavar "FILEPATH" <> help "Path to the wallet's database." From c1815b7dce5fb71d997bd3a94c4c5ccf2c9a9a94 Mon Sep 17 00:00:00 2001 From: KtorZ Date: Wed, 15 Apr 2020 14:24:42 +0200 Subject: [PATCH 11/11] show in the JSON export whether a passphrase is empty or not ```json $ export-wallets --mainnet --keyfile state-wallet-mainnet/secret.key [ { "encrypted_root_private_key": "18b952a968ccbac047f9c227dd05fced84ba85a1f83daf54f0a3c0de3e5664502678b1ba34a0b44ce22a2b54f39830570bdfeeed0a2f8586aa988fa5399987252b81f30b3c1e4c39e1edb16a4c7975eea106fc5d12d3b4fa516b9332f777defba949a42ef709a2d2f5c64057e99987c545b6e014f8cd9502bd5b800ddc6e10b7", "is_passphrase_empty": true, "name": null, "id": "f1aaaf1e1e6db1255ec2d59d50c5db892ff20330", "passphrase_hash": "31347c387c317c5743424875746242496c6a66734d764934314a30727a79663076657375724954796376766a793150554e377452673d3d7c54753434596d6e547957546c5759674a3164494f7974474a7842632b432f7862507657382b5135356a38303d" }, { "encrypted_root_private_key": "6d9e6a5135d373fb62eadaf02bc8f5ecf35148806721c704c6dbfcf818ff958f1fc4a933542c423f5b8621cfe6404c4b208f2c96f6246082dffbc9fb1029a79ed33651377731e0e59e0233425a55257782c5adaa768da0567f43c1c6c0c18766ed0a547bb34eb472c120b170a8640279832ddf18002887f03c15dea59705422d", "is_passphrase_empty": false, "name": null, "id": "05377e480bd68a2da04f00c39ce5886e4746ed1f", "passphrase_hash": "31347c387c317c574342652b796362417576356c2b4258676a344a314c6343675375414c2f5653393661364e576a2b7550766655513d3d7c2f376738486c59723174734e394f6e4e753253302b6a65515a6b5437316b45414941366a515867386539493d" } ] ``` --- wallet/export-wallets/Main.hs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/wallet/export-wallets/Main.hs b/wallet/export-wallets/Main.hs index 01ddb020a37..4e0407a083e 100644 --- a/wallet/export-wallets/Main.hs +++ b/wallet/export-wallets/Main.hs @@ -24,6 +24,7 @@ import Pos.Core.NetworkMagic (makeNetworkMagic) import Pos.Crypto (EncryptedPass (..), EncryptedSecretKey (..)) import Pos.Crypto.Configuration (ProtocolMagic (..), ProtocolMagicId (..), RequiresNetworkMagic (..)) +import Pos.Crypto.Signing (checkPassMatches, emptyPassphrase) import Pos.Infra.InjectFail (mkFInjects) import Pos.Util.Trace (Trace (..)) import Pos.Util.UserSecret (readUserSecret) @@ -97,11 +98,14 @@ extractWallet pw = do newtype Export a = Export a deriving (Show) instance ToJSON (Export (Maybe WalletName, EncryptedSecretKey)) where - toJSON (Export (name, EncryptedSecretKey{eskPayload, eskHash})) = Json.object + toJSON (Export (name, esk@EncryptedSecretKey{eskPayload, eskHash})) = Json.object [ "id" .= base16 (mkWalletId eskPayload) , "name" .= (getWalletName <$> name) , "encrypted_root_private_key" .= base16 (unXPrv eskPayload) , "passphrase_hash" .= base16 (getEncryptedPass eskHash) + , "is_passphrase_empty" .= case checkPassMatches emptyPassphrase esk of + Nothing -> False + Just{} -> True ] where base16 = T.decodeUtf8 . convertToBase @ByteString @ByteString Base16