From d02dadea8c85ef6ce6a051a75a1fe73bbd098b71 Mon Sep 17 00:00:00 2001 From: Rodney Lorrimar Date: Tue, 20 Jul 2021 02:16:05 +0800 Subject: [PATCH 01/15] cabal: Add a flag to enable/disable scrypt --- lib/core/cardano-wallet-core.cabal | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/core/cardano-wallet-core.cabal b/lib/core/cardano-wallet-core.cabal index c05aeb20ddd..59de44a96ef 100644 --- a/lib/core/cardano-wallet-core.cabal +++ b/lib/core/cardano-wallet-core.cabal @@ -17,6 +17,10 @@ flag release default: False manual: True +flag scrypt + description: Enable compatibility support for legacy wallet passwords. + default: True + library default-language: Haskell2010 @@ -29,6 +33,9 @@ library -fwarn-redundant-constraints if (flag(release)) ghc-options: -O2 -Werror + if (flag(scrypt)) + cpp-options: -DHAVE_SCRYPT + build-depends: scrypt build-depends: aeson , async @@ -111,7 +118,6 @@ library , retry , safe , scientific - , scrypt , servant , servant-client , servant-server From 3c0f89c682bf9afb4be52406f8de8634315c13d6 Mon Sep 17 00:00:00 2001 From: Rodney Lorrimar Date: Thu, 29 Jul 2021 14:22:20 +0800 Subject: [PATCH 02/15] Replace scrypt with cryptonite for tests only I'm not too keen on changing the scrypt package in the main code, for the time being. But we can replace this package in the integration tests, without too much risk. --- lib/cli/src/Cardano/CLI.hs | 12 +- .../cardano-wallet-core-integration.cabal | 1 - .../src/Test/Integration/Framework/DSL.hs | 19 +- .../Integration/Scenario/API/Byron/Wallets.hs | 6 +- .../Scenario/API/Shelley/Wallets.hs | 14 +- .../Integration/Scenario/CLI/Byron/Wallets.hs | 8 +- .../Scenario/CLI/Shelley/Wallets.hs | 16 +- lib/core/bench/db-bench.hs | 3 +- lib/core/cardano-wallet-core.cabal | 6 + lib/core/src/Cardano/Byron/Codec/Cbor.hs | 12 +- lib/core/src/Cardano/Wallet.hs | 82 ++++---- lib/core/src/Cardano/Wallet/Api.hs | 2 +- lib/core/src/Cardano/Wallet/Api/Server.hs | 89 ++++---- lib/core/src/Cardano/Wallet/Api/Types.hs | 37 ++-- lib/core/src/Cardano/Wallet/DB.hs | 6 +- lib/core/src/Cardano/Wallet/DB/MVar.hs | 6 +- lib/core/src/Cardano/Wallet/DB/Sqlite.hs | 32 ++- lib/core/src/Cardano/Wallet/DB/Sqlite/TH.hs | 1 + .../src/Cardano/Wallet/DB/Sqlite/Types.hs | 4 +- .../Wallet/Primitive/AddressDerivation.hs | 182 +--------------- .../Primitive/AddressDerivation/Byron.hs | 22 +- .../Primitive/AddressDerivation/Icarus.hs | 13 +- .../Primitive/AddressDerivation/MintBurn.hs | 3 +- .../Primitive/AddressDerivation/Shared.hs | 18 +- .../Primitive/AddressDerivation/Shelley.hs | 22 +- .../Wallet/Primitive/AddressDiscovery.hs | 5 +- .../Primitive/AddressDiscovery/Random.hs | 3 +- .../Primitive/AddressDiscovery/Sequential.hs | 3 +- .../Primitive/AddressDiscovery/Shared.hs | 5 +- .../Cardano/Wallet/Primitive/Passphrase.hs | 129 ++++++++++++ .../Wallet/Primitive/Passphrase/Current.hs | 80 +++++++ .../Wallet/Primitive/Passphrase/Legacy.hs | 117 +++++++++++ .../Wallet/Primitive/Passphrase/Types.hs | 195 ++++++++++++++++++ .../src/Cardano/Wallet/Primitive/Types.hs | 21 +- lib/core/src/Cardano/Wallet/Transaction.hs | 5 +- .../Wallet/Api/ApiTPassphraseuser.json | 15 ++ .../test/unit/Cardano/Byron/Codec/CborSpec.hs | 4 +- .../test/unit/Cardano/Wallet/Api/Malformed.hs | 2 +- .../test/unit/Cardano/Wallet/Api/TypesSpec.hs | 37 ++-- .../test/unit/Cardano/Wallet/DB/Arbitrary.hs | 13 +- .../test/unit/Cardano/Wallet/DB/SqliteSpec.hs | 20 +- .../unit/Cardano/Wallet/DB/StateMachine.hs | 14 +- .../Primitive/AddressDerivation/ByronSpec.hs | 4 +- .../Primitive/AddressDerivation/IcarusSpec.hs | 3 +- .../Wallet/Primitive/AddressDerivationSpec.hs | 115 ++--------- .../Primitive/AddressDiscovery/RandomSpec.hs | 3 +- .../Wallet/Primitive/AddressDiscoverySpec.hs | 9 +- .../Wallet/Primitive/Passphrase/LegacySpec.hs | 61 ++++++ .../Wallet/Primitive/PassphraseSpec.hs | 176 ++++++++++++++++ .../src/Cardano/Wallet/Shelley/Transaction.hs | 4 +- .../Cardano/Wallet/Shelley/TransactionSpec.hs | 11 +- 51 files changed, 1133 insertions(+), 537 deletions(-) create mode 100644 lib/core/src/Cardano/Wallet/Primitive/Passphrase.hs create mode 100644 lib/core/src/Cardano/Wallet/Primitive/Passphrase/Current.hs create mode 100644 lib/core/src/Cardano/Wallet/Primitive/Passphrase/Legacy.hs create mode 100644 lib/core/src/Cardano/Wallet/Primitive/Passphrase/Types.hs create mode 100644 lib/core/test/data/Cardano/Wallet/Api/ApiTPassphraseuser.json create mode 100644 lib/core/test/unit/Cardano/Wallet/Primitive/Passphrase/LegacySpec.hs create mode 100644 lib/core/test/unit/Cardano/Wallet/Primitive/PassphraseSpec.hs diff --git a/lib/cli/src/Cardano/CLI.hs b/lib/cli/src/Cardano/CLI.hs index 73bcf8a1bdd..5f3a08a5e33 100644 --- a/lib/cli/src/Cardano/CLI.hs +++ b/lib/cli/src/Cardano/CLI.hs @@ -165,15 +165,11 @@ import Cardano.Wallet.Api.Types import Cardano.Wallet.Orphans () import Cardano.Wallet.Primitive.AddressDerivation - ( Depth (..) - , DerivationType (..) - , Index (..) - , Passphrase (..) - , PassphraseMaxLength - , PassphraseMinLength - ) + ( Depth (..), DerivationType (..), Index (..) ) import Cardano.Wallet.Primitive.AddressDiscovery.Sequential ( AddressPoolGap, defaultAddressPoolGap ) +import Cardano.Wallet.Primitive.Passphrase + ( Passphrase (..), PassphraseMaxLength, PassphraseMinLength ) import Cardano.Wallet.Primitive.SyncProgress ( SyncTolerance (..) ) import Cardano.Wallet.Primitive.Types @@ -795,7 +791,7 @@ cmdTransactionCreate isShelley mkTxClient mkWalletClient = res <- sendRequest wPort $ getWallet mkWalletClient $ ApiT wId case res of Right _ -> do - wPwd <- getPassphrase @"raw" "Please enter your passphrase: " + wPwd <- getPassphrase @"user" "Please enter your passphrase: " runClient wPort Aeson.encodePretty $ postTransaction mkTxClient (ApiT wId) diff --git a/lib/core-integration/cardano-wallet-core-integration.cabal b/lib/core-integration/cardano-wallet-core-integration.cabal index dfcb6210b04..9f067760670 100644 --- a/lib/core-integration/cardano-wallet-core-integration.cabal +++ b/lib/core-integration/cardano-wallet-core-integration.cabal @@ -81,7 +81,6 @@ library , resourcet , retry , say - , scrypt , serialise , string-interpolate , template-haskell diff --git a/lib/core-integration/src/Test/Integration/Framework/DSL.hs b/lib/core-integration/src/Test/Integration/Framework/DSL.hs index a172977cf23..62afaa1fe51 100644 --- a/lib/core-integration/src/Test/Integration/Framework/DSL.hs +++ b/lib/core-integration/src/Test/Integration/Framework/DSL.hs @@ -273,13 +273,13 @@ import Cardano.Wallet.Primitive.AddressDerivation , HardDerivation (..) , Index (..) , NetworkDiscriminant (..) - , Passphrase (..) , PaymentAddress (..) , PersistPublicKey (..) , Role (..) , WalletKey (..) , fromHex , hex +import Cardano.Wallet.Primitive.Passphrase ( Passphrase (..) , preparePassphrase ) import Cardano.Wallet.Primitive.AddressDerivation.Byron @@ -343,10 +343,14 @@ import Crypto.Hash ( Blake2b_160, Digest, digestFromByteString ) import Crypto.Hash.Utils ( blake2b224 ) +import Crypto.Random.Entropy + ( getEntropy ) import Data.Aeson ( FromJSON, ToJSON, Value, (.=) ) import Data.Aeson.QQ ( aesonQQ ) +import Data.ByteArray.Encoding + ( Base (..), convertFromBase, convertToBase ) import Data.ByteString ( ByteString ) import Data.Either.Extra @@ -459,7 +463,7 @@ import qualified Cardano.Wallet.Primitive.Types.TokenQuantity as TokenQuantity import qualified Codec.Binary.Bech32 as Bech32 import qualified Codec.CBOR.Encoding as CBOR import qualified Codec.CBOR.Write as CBOR -import qualified Crypto.Scrypt as Scrypt +import qualified Crypto.KDF.Scrypt as Scrypt import qualified Data.Aeson as Aeson import qualified Data.ByteArray as BA import qualified Data.ByteString as BS @@ -1280,17 +1284,8 @@ emptyRandomWalletWithPasswd ctx rawPwd = do $ hex $ Byron.getKey $ Byron.generateKeyFromSeed seed pwd - pwdH <- liftIO $ T.decodeUtf8 . hex <$> encryptPasswordWithScrypt pwd + pwdH <- encryptPassphraseTestingOnly pwd emptyByronWalletFromXPrvWith ctx "random" ("Random Wallet", key, pwdH) - where - encryptPasswordWithScrypt = - fmap Scrypt.getEncryptedPass - . Scrypt.encryptPassIO Scrypt.defaultParams - . Scrypt.Pass - . CBOR.toStrictByteString - . CBOR.encodeBytes - . BA.convert - postWallet' :: (MonadIO m, MonadUnliftIO m) diff --git a/lib/core-integration/src/Test/Integration/Scenario/API/Byron/Wallets.hs b/lib/core-integration/src/Test/Integration/Scenario/API/Byron/Wallets.hs index dfeaadd5abc..4b14a649d9c 100644 --- a/lib/core-integration/src/Test/Integration/Scenario/API/Byron/Wallets.hs +++ b/lib/core-integration/src/Test/Integration/Scenario/API/Byron/Wallets.hs @@ -63,6 +63,7 @@ import Test.Integration.Framework.DSL , emptyIcarusWallet , emptyRandomWallet , emptyRandomWalletWithPasswd + , encryptWalletPasswordWithScrypt , eventually , expectErrorMessage , expectField @@ -78,6 +79,7 @@ import Test.Integration.Framework.DSL , fixtureRandomWallet , genMnemonics , getFromResponse + , getSaltFromHexScryptPassword , json , listFilteredByronWallets , postByronWallet @@ -287,8 +289,8 @@ spec = describe "BYRON_WALLETS" $ do ] describe "BYRON_RESTORE_06 - Passphrase" $ do - let minLength = passphraseMinLength (Proxy @"raw") - let maxLength = passphraseMaxLength (Proxy @"raw") + let minLength = passphraseMinLength (Proxy @"user") + let maxLength = passphraseMaxLength (Proxy @"user") let matrix = [ ( show minLength ++ " char long" , T.pack (replicate minLength 'ź') diff --git a/lib/core-integration/src/Test/Integration/Scenario/API/Shelley/Wallets.hs b/lib/core-integration/src/Test/Integration/Scenario/API/Shelley/Wallets.hs index ed9215b0ef6..8eca83d659e 100644 --- a/lib/core-integration/src/Test/Integration/Scenario/API/Shelley/Wallets.hs +++ b/lib/core-integration/src/Test/Integration/Scenario/API/Shelley/Wallets.hs @@ -411,8 +411,8 @@ spec = describe "SHELLEY_WALLETS" $ do verify r [ expectResponseCode HTTP.status201 ] describe "WALLETS_CREATE_07 - Passphrase" $ do - let minLength = passphraseMinLength (Proxy @"raw") - let maxLength = passphraseMaxLength (Proxy @"raw") + let minLength = passphraseMinLength (Proxy @"user") + let maxLength = passphraseMaxLength (Proxy @"user") let matrix = [ ( show minLength ++ " char long" , T.pack (replicate minLength 'ź') ) @@ -768,8 +768,8 @@ spec = describe "SHELLEY_WALLETS" $ do expectField #passphrase (`shouldNotBe` originalPassUpdateDateTime) rg describe "WALLETS_UPDATE_PASS_02 - New passphrase values" $ do - let minLength = passphraseMinLength (Proxy @"raw") - let maxLength = passphraseMaxLength (Proxy @"raw") + let minLength = passphraseMinLength (Proxy @"user") + let maxLength = passphraseMaxLength (Proxy @"user") let matrix = [ ( show minLength ++ " char long" , T.pack (replicate minLength 'ź') @@ -815,8 +815,8 @@ spec = describe "SHELLEY_WALLETS" $ do describe "WALLETS_UPDATE_PASS_03 - Can update pass from pass that's boundary\ \ value" $ do - let minLength = passphraseMinLength (Proxy @"raw") - let maxLength = passphraseMaxLength (Proxy @"raw") + let minLength = passphraseMinLength (Proxy @"user") + let maxLength = passphraseMaxLength (Proxy @"user") let matrix = [ ( show minLength ++ " char long" , T.pack (replicate minLength 'ź') ) @@ -836,7 +836,7 @@ spec = describe "SHELLEY_WALLETS" $ do "passphrase": #{oldPass} } |] w <- unsafeResponse <$> postWallet ctx createPayload - let len = passphraseMaxLength (Proxy @"raw") + let len = passphraseMaxLength (Proxy @"user") let newPass = T.pack $ replicate len '💘' let payload = updatePassPayload oldPass newPass rup <- request @ApiWallet ctx diff --git a/lib/core-integration/src/Test/Integration/Scenario/CLI/Byron/Wallets.hs b/lib/core-integration/src/Test/Integration/Scenario/CLI/Byron/Wallets.hs index b6578de7fec..ef3c6a55379 100644 --- a/lib/core-integration/src/Test/Integration/Scenario/CLI/Byron/Wallets.hs +++ b/lib/core-integration/src/Test/Integration/Scenario/CLI/Byron/Wallets.hs @@ -218,8 +218,8 @@ spec = describe "BYRON_CLI_WALLETS" $ do it' "ledger" (genMnemonics M24) scenarioSuccess -- ✔️ describe "CLI_BYRON_RESTORE_06 - Passphrase" $ do - let minLength = passphraseMinLength (Proxy @"raw") - let maxLength = passphraseMaxLength (Proxy @"raw") + let minLength = passphraseMinLength (Proxy @"user") + let maxLength = passphraseMaxLength (Proxy @"user") let matrix = [ ( show minLength ++ " char long" , T.pack (replicate minLength 'ź') @@ -311,8 +311,8 @@ spec = describe "BYRON_CLI_WALLETS" $ do T.unpack e `shouldContain` errMsg403WrongPass describe "CLI_BYRON_UPDATE_PASS_03 - Pass length incorrect" $ do - let minLength = passphraseMinLength (Proxy @"raw") - let maxLength = passphraseMaxLength (Proxy @"raw") + let minLength = passphraseMinLength (Proxy @"user") + let maxLength = passphraseMaxLength (Proxy @"user") let passTooShort = replicate (minLength - 1) 'o' let errMsgTooShort = "passphrase is too short: expected at least 10 characters" let passTooLong = replicate (maxLength + 1) 'o' diff --git a/lib/core-integration/src/Test/Integration/Scenario/CLI/Shelley/Wallets.hs b/lib/core-integration/src/Test/Integration/Scenario/CLI/Shelley/Wallets.hs index 36c67f22813..3b65d5b2012 100644 --- a/lib/core-integration/src/Test/Integration/Scenario/CLI/Shelley/Wallets.hs +++ b/lib/core-integration/src/Test/Integration/Scenario/CLI/Shelley/Wallets.hs @@ -343,7 +343,7 @@ spec = describe "SHELLEY_CLI_WALLETS" $ do \ words are expected." describe "WALLETS_CREATE_07 - Passphrase is valid" $ do - let proxy_ = Proxy @"raw" + let proxy_ = Proxy @"user" let passphraseMax = replicate (passphraseMaxLength proxy_) 'ą' let passBelowMax = replicate (passphraseMaxLength proxy_ - 1) 'ć' let passphraseMin = replicate (passphraseMinLength proxy_) 'ń' @@ -366,7 +366,7 @@ spec = describe "SHELLEY_CLI_WALLETS" $ do expectCliField #passphrase (`shouldNotBe` Nothing) j describe "WALLETS_CREATE_07 - When passphrase is invalid" $ do - let proxy_ = Proxy @"raw" + let proxy_ = Proxy @"user" let passAboveMax = replicate (passphraseMaxLength proxy_ + 1) 'ą' let passBelowMin = replicate (passphraseMinLength proxy_ - 1) 'ń' let passMinWarn = "passphrase is too short: expected at \ @@ -540,8 +540,8 @@ spec = describe "SHELLEY_CLI_WALLETS" $ do expectCliField #passphrase (`shouldNotBe` initPassUpdateTime) j describe "WALLETS_UPDATE_PASS_02 - New passphrase values" $ do - let minLength = passphraseMinLength (Proxy @"raw") - let maxLength = passphraseMaxLength (Proxy @"raw") + let minLength = passphraseMinLength (Proxy @"user") + let maxLength = passphraseMaxLength (Proxy @"user") let matrix = [ ( show minLength ++ " char long" , replicate minLength 'ź' @@ -598,8 +598,8 @@ spec = describe "SHELLEY_CLI_WALLETS" $ do exitCode `shouldBe` ExitFailure 1 describe "WALLETS_UPDATE_PASS_03 - Old passphrase values" $ do - let minLength = passphraseMinLength (Proxy @"raw") - let maxLength = passphraseMaxLength (Proxy @"raw") + let minLength = passphraseMinLength (Proxy @"user") + let maxLength = passphraseMaxLength (Proxy @"user") let matrix = [ ( show (minLength - 1) ++ " char long" , replicate (minLength - 1) 'ź' @@ -631,8 +631,8 @@ spec = describe "SHELLEY_CLI_WALLETS" $ do describe "WALLETS_UPDATE_PASS_03 - \ \Can update pass from pass that's boundary value" $ do - let minLength = passphraseMinLength (Proxy @"raw") - let maxLength = passphraseMaxLength (Proxy @"raw") + let minLength = passphraseMinLength (Proxy @"user") + let maxLength = passphraseMaxLength (Proxy @"user") let matrix = [ ( show minLength ++ " char long" , replicate minLength 'ź' diff --git a/lib/core/bench/db-bench.hs b/lib/core/bench/db-bench.hs index 5bc6932e8ee..581cc3b34b9 100644 --- a/lib/core/bench/db-bench.hs +++ b/lib/core/bench/db-bench.hs @@ -84,7 +84,6 @@ import Cardano.Wallet.Primitive.AddressDerivation , Depth (..) , Index (..) , NetworkDiscriminant (..) - , Passphrase (..) , PaymentAddress (..) , PersistPrivateKey , WalletKey (..) @@ -107,6 +106,8 @@ import Cardano.Wallet.Primitive.AddressDiscovery.Sequential ) import Cardano.Wallet.Primitive.Model ( Wallet, initWallet, unsafeInitWallet ) +import Cardano.Wallet.Primitive.Passphrase + ( Passphrase (..) ) import Cardano.Wallet.Primitive.Slotting ( TimeInterpreter, hoistTimeInterpreter, mkSingleEraInterpreter ) import Cardano.Wallet.Primitive.Types diff --git a/lib/core/cardano-wallet-core.cabal b/lib/core/cardano-wallet-core.cabal index 59de44a96ef..3cc407e68df 100644 --- a/lib/core/cardano-wallet-core.cabal +++ b/lib/core/cardano-wallet-core.cabal @@ -228,6 +228,10 @@ library Cardano.Wallet.Primitive.Model Cardano.Wallet.Primitive.Slotting Cardano.Wallet.Primitive.SyncProgress + Cardano.Wallet.Primitive.Passphrase + Cardano.Wallet.Primitive.Passphrase.Current + Cardano.Wallet.Primitive.Passphrase.Legacy + Cardano.Wallet.Primitive.Passphrase.Types Cardano.Wallet.Primitive.Types Cardano.Wallet.Primitive.Types.Address Cardano.Wallet.Primitive.Types.Coin @@ -462,6 +466,8 @@ test-suite unit Cardano.Wallet.Primitive.Migration.PlanningSpec Cardano.Wallet.Primitive.Migration.SelectionSpec Cardano.Wallet.Primitive.ModelSpec + Cardano.Wallet.Primitive.PassphraseSpec + Cardano.Wallet.Primitive.Passphrase.LegacySpec Cardano.Wallet.Primitive.Slotting.Legacy Cardano.Wallet.Primitive.SlottingSpec Cardano.Wallet.Primitive.SyncProgressSpec diff --git a/lib/core/src/Cardano/Byron/Codec/Cbor.hs b/lib/core/src/Cardano/Byron/Codec/Cbor.hs index 7b1fd95a8d4..320a8b28ee5 100644 --- a/lib/core/src/Cardano/Byron/Codec/Cbor.hs +++ b/lib/core/src/Cardano/Byron/Codec/Cbor.hs @@ -46,7 +46,9 @@ import Prelude import Cardano.Address.Derivation ( XPub, xpubToBytes ) import Cardano.Wallet.Primitive.AddressDerivation - ( Depth (..), DerivationType (..), Index (..), Passphrase (..) ) + ( Depth (..), DerivationType (..), Index (..) ) +import Cardano.Wallet.Primitive.Passphrase + ( Passphrase (..) ) import Cardano.Wallet.Primitive.Types ( ProtocolMagic (..) ) import Cardano.Wallet.Primitive.Types.Address @@ -425,9 +427,9 @@ encryptDerivationPath -- ^ Payload to be encrypted -> ByteString -- ^ Ciphertext with a 128-bit crypto-tag appended. -encryptDerivationPath (Passphrase passphrase) payload = unsafeSerialize $ do +encryptDerivationPath passphrase payload = unsafeSerialize $ do nonce <- Poly.nonce12 cardanoNonce - st1 <- Poly.finalizeAAD <$> Poly.initialize passphrase nonce + st1 <- Poly.finalizeAAD <$> Poly.initialize (unPassphrase passphrase) nonce let (out, st2) = Poly.encrypt (CBOR.toStrictByteString payload) st1 return $ out <> BA.convert (Poly.finalize st2) where @@ -449,10 +451,10 @@ decryptDerivationPath -> ByteString -- ^ Payload to be decrypted -> CryptoFailable ByteString -decryptDerivationPath (Passphrase passphrase) bytes = do +decryptDerivationPath passphrase bytes = do let (payload, tag) = BS.splitAt (BS.length bytes - 16) bytes nonce <- Poly.nonce12 cardanoNonce - st1 <- Poly.finalizeAAD <$> Poly.initialize passphrase nonce + st1 <- Poly.finalizeAAD <$> Poly.initialize (unPassphrase passphrase) nonce let (out, st2) = Poly.decrypt payload st1 when (BA.convert (Poly.finalize st2) /= tag) $ CryptoFailed CryptoError_MacKeyInvalid diff --git a/lib/core/src/Cardano/Wallet.hs b/lib/core/src/Cardano/Wallet.hs index 827da9e1369..9d09d6aad92 100644 --- a/lib/core/src/Cardano/Wallet.hs +++ b/lib/core/src/Cardano/Wallet.hs @@ -286,23 +286,18 @@ import Cardano.Wallet.Primitive.AddressDerivation , DerivationIndex (..) , DerivationPrefix (..) , DerivationType (..) - , ErrWrongPassphrase (..) , HardDerivation (..) , Index (..) , MkKeyFingerprint (..) , NetworkDiscriminant (..) - , Passphrase , PaymentAddress (..) , Role (..) , SoftDerivation (..) , ToRewardAccount (..) , WalletKey (..) - , checkPassphrase , deriveRewardAccount - , encryptPassphrase , hashVerificationKey , liftIndex - , preparePassphrase , stakeDerivationPath ) import Cardano.Wallet.Primitive.AddressDerivation.Byron @@ -352,6 +347,17 @@ import Cardano.Wallet.Primitive.Model , totalUTxO , updateState ) +import Cardano.Wallet.Primitive.Passphrase + ( ErrWrongPassphrase (..) + , Passphrase + , PassphraseHash + , PassphraseScheme (..) + , WalletPassphraseInfo (..) + , checkPassphrase + , currentPassphraseScheme + , encryptPassphrase' + , preparePassphrase + ) import Cardano.Wallet.Primitive.Slotting ( PastHorizonException (..) , TimeInterpreter @@ -377,7 +383,6 @@ import Cardano.Wallet.Primitive.Types , IsDelegatingTo (..) , LinearFunction (LinearFunction) , NetworkParameters (..) - , PassphraseScheme (..) , PoolId (..) , PoolLifeCycleStatus (..) , ProtocolParameters (..) @@ -391,7 +396,6 @@ import Cardano.Wallet.Primitive.Types , WalletId (..) , WalletMetadata (..) , WalletName (..) - , WalletPassphraseInfo (..) , WithOrigin (..) , dlgCertPoolId , toSlot @@ -503,8 +507,6 @@ import Crypto.Hash ( Blake2b_256, hash ) import Data.ByteString ( ByteString ) -import Data.Coerce - ( coerce ) import Data.DBVar ( modifyDBMaybe ) import Data.Either @@ -876,20 +878,18 @@ updateWalletPassphrase ) => ctx -> WalletId - -> (Passphrase "raw", Passphrase "raw") + -> (Passphrase "user", Passphrase "user") -> ExceptT ErrUpdatePassphrase IO () updateWalletPassphrase ctx wid (old, new) = - withRootKey @ctx @s @k ctx wid (coerce old) ErrUpdatePassphraseWithRootKey + withRootKey @ctx @s @k ctx wid old ErrUpdatePassphraseWithRootKey $ \xprv scheme -> withExceptT ErrUpdatePassphraseNoSuchWallet $ do - -- NOTE - -- /!\ Important /!\ - -- attachPrivateKeyFromPwd does use 'EncryptWithPBKDF2', so - -- regardless of the passphrase current scheme, we'll re-encrypt - -- it using the new scheme, always. - let oldP = preparePassphrase scheme old - let newP = preparePassphrase EncryptWithPBKDF2 new - let xprv' = changePassphrase oldP newP xprv - attachPrivateKeyFromPwd @ctx @s @k ctx wid (xprv', newP) + -- IMPORTANT NOTE: + -- This use 'EncryptWithPBKDF2', regardless of the passphrase + -- current scheme, we'll re-encrypt it using the current scheme, + -- always. + let new' = (currentPassphraseScheme, new) + let xprv' = changePassphrase (scheme, old) new' xprv + attachPrivateKeyFromPwdScheme @ctx @s @k ctx wid (xprv', new') getWalletUtxoSnapshot :: forall ctx s k. @@ -1424,7 +1424,7 @@ createRandomAddress ) => ctx -> WalletId - -> Passphrase "raw" + -> Passphrase "user" -> Maybe (Index 'Hardened 'AddressK) -> ExceptT ErrCreateRandomAddress IO (Address, NonEmpty DerivationIndex) createRandomAddress ctx wid pwd mIx = db & \DBLayer{..} -> @@ -2360,7 +2360,7 @@ buildAndSignTransaction ( XPrv, Passphrase "encryption") ) -- ^ Reward account derived from the root key (or somewhere else). - -> Passphrase "raw" + -> Passphrase "user" -> TransactionCtx -> SelectionOf TxOut -> ExceptT ErrSignPayment IO (Tx, TxMeta, UTCTime, SealedTx) @@ -3055,17 +3055,17 @@ estimateFee Key Store -------------------------------------------------------------------------------} -- | The password here undergoes PBKDF2 encryption using HMAC --- with the hash algorithm SHA512 which is realized in encryptPassphare -attachPrivateKeyFromPwd +-- with the hash algorithm SHA512 which is realized in encryptPassphrase +attachPrivateKeyFromPwdScheme :: forall ctx s k. ( HasDBLayer IO s k ctx ) => ctx -> WalletId - -> (k 'RootK XPrv, Passphrase "encryption") + -> (k 'RootK XPrv, (PassphraseScheme, Passphrase "user")) -> ExceptT ErrNoSuchWallet IO () -attachPrivateKeyFromPwd ctx wid (xprv, pwd) = db & \_ -> do - hpwd <- liftIO $ encryptPassphrase pwd +attachPrivateKeyFromPwdScheme ctx wid (xprv, (scheme, pwd)) = db & \_ -> do + hpwd <- liftIO $ encryptPassphrase' scheme pwd -- NOTE Only new wallets are constructed through this function, so the -- passphrase is encrypted with the new scheme (i.e. PBKDF2) -- @@ -3075,8 +3075,8 @@ attachPrivateKeyFromPwd ctx wid (xprv, pwd) = db & \_ -> do -- this function with a 'Passphrase' that wasn't prepared for -- 'EncryptWithPBKDF2', if this happens, this is a programmer error and we -- must fail hard for this would have dramatic effects later on. - case checkPassphrase EncryptWithPBKDF2 (coerce pwd) hpwd of - Right () -> attachPrivateKey db wid (xprv, hpwd) EncryptWithPBKDF2 + case checkPassphrase scheme pwd hpwd of + Right () -> attachPrivateKey db wid (xprv, hpwd) scheme Left{} -> fail "Awe crap! The passphrase given to 'attachPrivateKeyFromPwd' wasn't \ \rightfully constructed. This is a programmer error. Look for calls \ @@ -3085,6 +3085,18 @@ attachPrivateKeyFromPwd ctx wid (xprv, pwd) = db & \_ -> do where db = ctx ^. dbLayer @IO @s @k +attachPrivateKeyFromPwd + :: forall ctx s k. + ( HasDBLayer IO s k ctx + ) + => ctx + -> WalletId + -> (k 'RootK XPrv, Passphrase "user") + -> ExceptT ErrNoSuchWallet IO () +attachPrivateKeyFromPwd ctx wid (xprv, pwd) = + attachPrivateKeyFromPwdScheme @ctx @s @k ctx wid + (xprv, (currentPassphraseScheme, pwd)) + -- | The hash here is the output of Scrypt function with the following parameters: -- - logN = 14 -- - r = 8 @@ -3096,7 +3108,7 @@ attachPrivateKeyFromPwdHash ) => ctx -> WalletId - -> (k 'RootK XPrv, Hash "encryption") + -> (k 'RootK XPrv, PassphraseHash) -> ExceptT ErrNoSuchWallet IO () attachPrivateKeyFromPwdHash ctx wid (xprv, hpwd) = db & \_ -> -- NOTE Only legacy wallets are imported through this function, passphrase @@ -3108,7 +3120,7 @@ attachPrivateKeyFromPwdHash ctx wid (xprv, hpwd) = db & \_ -> attachPrivateKey :: DBLayer IO s k -> WalletId - -> (k 'RootK XPrv, Hash "encryption") + -> (k 'RootK XPrv, PassphraseHash) -> PassphraseScheme -> ExceptT ErrNoSuchWallet IO () attachPrivateKey db wid (xprv, hpwd) scheme = db & \DBLayer{..} -> do @@ -3144,7 +3156,7 @@ withRootKey :: forall ctx s k e a. HasDBLayer IO s k ctx => ctx -> WalletId - -> Passphrase "raw" + -> Passphrase "user" -> (ErrWithRootKey -> e) -> (k 'RootK XPrv -> PassphraseScheme -> ExceptT e IO a) -> ExceptT e IO a @@ -3179,7 +3191,7 @@ signMetadataWith ) => ctx -> WalletId - -> Passphrase "raw" + -> Passphrase "user" -> (Role, DerivationIndex) -> TxMetadata -> ExceptT ErrSignMetadataWith IO (Signature TxMetadata) @@ -3256,7 +3268,7 @@ writePolicyPublicKey ) => ctx -> WalletId - -> Passphrase "raw" + -> Passphrase "user" -> ExceptT ErrWritePolicyPublicKey IO (ShelleyKey 'PolicyK XPub) writePolicyPublicKey ctx wid pwd = db & \DBLayer{..} -> do cp <- mapExceptT atomically @@ -3291,7 +3303,7 @@ getAccountPublicKeyAtIndex ) => ctx -> WalletId - -> Passphrase "raw" + -> Passphrase "user" -> DerivationIndex -> Maybe DerivationIndex -> ExceptT ErrReadAccountPublicKey IO (k 'AccountK XPub) diff --git a/lib/core/src/Cardano/Wallet/Api.hs b/lib/core/src/Cardano/Wallet/Api.hs index f6b39c56960..849c85559c2 100644 --- a/lib/core/src/Cardano/Wallet/Api.hs +++ b/lib/core/src/Cardano/Wallet/Api.hs @@ -632,7 +632,7 @@ type ShelleyMigrations n = type MigrateShelleyWallet n = "wallets" :> Capture "walletId" (ApiT WalletId) :> "migrations" - :> ReqBody '[JSON] (ApiWalletMigrationPostDataT n "raw") + :> ReqBody '[JSON] (ApiWalletMigrationPostDataT n "user") :> PostAccepted '[JSON] (NonEmpty (ApiTransactionT n)) -- | https://input-output-hk.github.io/cardano-wallet/api/#operation/createShelleyWalletMigrationPlan diff --git a/lib/core/src/Cardano/Wallet/Api/Server.hs b/lib/core/src/Cardano/Wallet/Api/Server.hs index bbca9044251..36fb6cb9ddd 100644 --- a/lib/core/src/Cardano/Wallet/Api/Server.hs +++ b/lib/core/src/Cardano/Wallet/Api/Server.hs @@ -352,7 +352,6 @@ import Cardano.Wallet.Primitive.AddressDerivation , Index (..) , MkKeyFingerprint , NetworkDiscriminant (..) - , Passphrase (..) , PaymentAddress (..) , RewardAccount (..) , Role @@ -360,7 +359,6 @@ import Cardano.Wallet.Primitive.AddressDerivation , WalletKey (..) , deriveRewardAccount , digest - , preparePassphrase , publicKey ) import Cardano.Wallet.Primitive.AddressDerivation.Byron @@ -417,6 +415,13 @@ import Cardano.Wallet.Primitive.Model , totalBalance , totalUTxO ) +import Cardano.Wallet.Primitive.Passphrase + ( Passphrase (..) + , PassphraseScheme (..) + , WalletPassphraseInfo (..) + , currentPassphraseScheme + , preparePassphrase + ) import Cardano.Wallet.Primitive.Slotting ( PastHorizonException , RelativeTime @@ -440,7 +445,6 @@ import Cardano.Wallet.Primitive.Types ( Block , BlockHeader (..) , NetworkParameters (..) - , PassphraseScheme (..) , PoolId , PoolLifeCycleStatus (..) , Signature (..) @@ -824,7 +828,7 @@ postShelleyWallet -> WalletPostData -> Handler ApiWallet postShelleyWallet ctx generateKey body = do - let state = mkSeqStateFromRootXPrv (rootXPrv, pwd) purposeCIP1852 g + let state = mkSeqStateFromRootXPrv (rootXPrv, pwdP) purposeCIP1852 g void $ liftHandler $ createWalletWorker @_ @s @k ctx wid (\wrk -> W.createWallet @(WorkerCtx ctx) @_ @s @k wrk wid wName state) (\wrk _ -> W.manageRewardBalance @(WorkerCtx ctx) @s @k (Proxy @n) wrk wid) @@ -834,8 +838,9 @@ postShelleyWallet ctx generateKey body = do where seed = getApiMnemonicT (body ^. #mnemonicSentence) secondFactor = getApiMnemonicT <$> (body ^. #mnemonicSecondFactor) - pwd = preparePassphrase EncryptWithPBKDF2 $ getApiT (body ^. #passphrase) - rootXPrv = generateKey (seed, secondFactor) pwd + pwd = getApiT (body ^. #passphrase) + pwdP = preparePassphrase currentPassphraseScheme pwd + rootXPrv = generateKey (seed, secondFactor) pwdP g = maybe defaultAddressPoolGap getApiT (body ^. #addressPoolGap) wid = WalletId $ digest $ publicKey rootXPrv wName = getApiT (body ^. #name) @@ -1008,7 +1013,7 @@ postSharedWalletFromRootXPrv ctx generateKey body = do Right _ -> pure () ix' <- liftHandler $ withExceptT ErrConstructSharedWalletInvalidIndex $ W.guardHardIndex ix - let state = mkSharedStateFromRootXPrv (rootXPrv, pwd) ix' g pTemplate dTemplateM + let state = mkSharedStateFromRootXPrv (rootXPrv, pwdP) ix' g pTemplate dTemplateM void $ liftHandler $ createWalletWorker @_ @s @k ctx wid (\wrk -> W.createWallet @(WorkerCtx ctx) @_ @s @k wrk wid wName state) idleWorker @@ -1016,17 +1021,18 @@ postSharedWalletFromRootXPrv ctx generateKey body = do W.attachPrivateKeyFromPwd @_ @s @k wrk wid (rootXPrv, pwd) fst <$> getWallet ctx (mkSharedWallet @_ @s @k) (ApiT wid) where - seed = getApiMnemonicT (body ^. #mnemonicSentence) - secondFactor = getApiMnemonicT <$> (body ^. #mnemonicSecondFactor) - pwd = preparePassphrase EncryptWithPBKDF2 $ getApiT (body ^. #passphrase) - rootXPrv = generateKey (seed, secondFactor) pwd + seed = body ^. #mnemonicSentence . #getApiMnemonicT + secondFactor = getApiMnemonicT <$> body ^. #mnemonicSecondFactor + pwdP = preparePassphrase currentPassphraseScheme pwd + pwd = body ^. #passphrase . #getApiT + rootXPrv = generateKey (seed, secondFactor) pwdP g = defaultAddressPoolGap ix = getApiT (body ^. #accountIndex) wid = WalletId $ digest $ publicKey rootXPrv pTemplate = scriptTemplateFromSelf (getRawKey accXPub) $ body ^. #paymentScriptTemplate dTemplateM = scriptTemplateFromSelf (getRawKey accXPub) <$> body ^. #delegationScriptTemplate wName = getApiT (body ^. #name) - accXPub = publicKey $ deriveAccountPrivateKey pwd rootXPrv (Index $ getDerivationIndex ix) + accXPub = publicKey $ deriveAccountPrivateKey pwdP rootXPrv (Index $ getDerivationIndex ix) scriptValidation = maybe RecommendedValidation getApiT (body ^. #scriptValidation) postSharedWalletFromAccountXPub @@ -1176,7 +1182,7 @@ postLegacyWallet ) => ctx -- ^ Surrounding Context - -> (k 'RootK XPrv, Passphrase "encryption") + -> (k 'RootK XPrv, Passphrase "user") -- ^ Root key -> ( WorkerCtx ctx -> WalletId @@ -1221,9 +1227,9 @@ mkLegacyWallet ctx wid cp meta pending progress = do pwdInfo <- case meta ^. #passphraseInfo of Nothing -> pure Nothing - Just (W.WalletPassphraseInfo time EncryptWithPBKDF2) -> + Just (WalletPassphraseInfo time EncryptWithPBKDF2) -> pure $ Just $ ApiWalletPassphraseInfo time - Just (W.WalletPassphraseInfo time EncryptWithScrypt) -> do + Just (WalletPassphraseInfo time EncryptWithScrypt) -> do withWorkerCtx @_ @s @k ctx wid liftE liftE $ matchEmptyPassphrase >=> \case Right{} -> pure Nothing @@ -1272,10 +1278,11 @@ postRandomWallet ctx body = do postLegacyWallet ctx (rootXPrv, pwd) $ \wrk wid -> W.createWallet @(WorkerCtx ctx) @_ @s @k wrk wid wName s where - wName = getApiT (body ^. #name) - pwd = preparePassphrase EncryptWithPBKDF2 $ getApiT (body ^. #passphrase) - rootXPrv = Byron.generateKeyFromSeed seed pwd - where seed = getApiMnemonicT (body ^. #mnemonicSentence) + wName = body ^. #name . #getApiT + seed = body ^. #mnemonicSentence . #getApiMnemonicT + pwd = body ^. #passphrase . #getApiT + pwdP = preparePassphrase currentPassphraseScheme pwd + rootXPrv = Byron.generateKeyFromSeed seed pwdP postRandomWalletFromXPrv :: forall ctx s k n. @@ -1316,12 +1323,14 @@ postIcarusWallet -> Handler ApiByronWallet postIcarusWallet ctx body = do postLegacyWallet ctx (rootXPrv, pwd) $ \wrk wid -> - W.createIcarusWallet @(WorkerCtx ctx) @s @k wrk wid wName (rootXPrv, pwd) + W.createIcarusWallet @(WorkerCtx ctx) @s @k wrk wid wName + (rootXPrv, pwdP) where - wName = getApiT (body ^. #name) - pwd = preparePassphrase EncryptWithPBKDF2 $ getApiT (body ^. #passphrase) - rootXPrv = Icarus.generateKeyFromSeed seed pwd - where seed = getApiMnemonicT (body ^. #mnemonicSentence) + wName = body ^. #name . #getApiT + seed = body ^. #mnemonicSentence . #getApiMnemonicT + pwd = body ^. #passphrase . #getApiT + pwdP = preparePassphrase currentPassphraseScheme pwd + rootXPrv = Icarus.generateKeyFromSeed seed pwdP postTrezorWallet :: forall ctx s k n. @@ -1337,12 +1346,14 @@ postTrezorWallet -> Handler ApiByronWallet postTrezorWallet ctx body = do postLegacyWallet ctx (rootXPrv, pwd) $ \wrk wid -> - W.createIcarusWallet @(WorkerCtx ctx) @s @k wrk wid wName (rootXPrv, pwd) + W.createIcarusWallet @(WorkerCtx ctx) @s @k wrk wid wName + (rootXPrv, pwdP) where - wName = getApiT (body ^. #name) - pwd = preparePassphrase EncryptWithPBKDF2 $ getApiT (body ^. #passphrase) - rootXPrv = Icarus.generateKeyFromSeed seed pwd - where seed = getApiMnemonicT (body ^. #mnemonicSentence) + wName = body ^. #name . #getApiT + seed = body ^. #mnemonicSentence . #getApiMnemonicT + pwd = body ^. #passphrase . #getApiT + pwdP = preparePassphrase currentPassphraseScheme pwd + rootXPrv = Icarus.generateKeyFromSeed seed pwdP postLedgerWallet :: forall ctx s k n. @@ -1358,12 +1369,14 @@ postLedgerWallet -> Handler ApiByronWallet postLedgerWallet ctx body = do postLegacyWallet ctx (rootXPrv, pwd) $ \wrk wid -> - W.createIcarusWallet @(WorkerCtx ctx) @s @k wrk wid wName (rootXPrv, pwd) + W.createIcarusWallet @(WorkerCtx ctx) @s @k wrk wid wName + (rootXPrv, pwdP) where - wName = getApiT (body ^. #name) - pwd = preparePassphrase EncryptWithPBKDF2 $ getApiT (body ^. #passphrase) - rootXPrv = Icarus.generateKeyFromHardwareLedger mw pwd - where mw = getApiMnemonicT (body ^. #mnemonicSentence) + wName = body ^. #name . #getApiT + mw = body ^. #mnemonicSentence . #getApiMnemonicT + pwd = body ^. #passphrase . #getApiT + pwdP = preparePassphrase currentPassphraseScheme pwd + rootXPrv = Icarus.generateKeyFromHardwareLedger mw pwdP {------------------------------------------------------------------------------- ApiLayer Discrimination @@ -3393,7 +3406,7 @@ rndStateChange ) => ctx -> ApiT WalletId - -> Passphrase "raw" + -> Passphrase "user" -> Handler (ArgGenChange s) rndStateChange ctx (ApiT wid) pwd = withWorkerCtx @_ @s @k ctx wid liftE liftE $ \wrk -> liftHandler $ @@ -4058,6 +4071,12 @@ instance IsServerError ErrWithRootKey where , "to encrypt the root private key of the given wallet: " , toText wid ] + ErrWithRootKeyWrongPassphrase wid (ErrPassphraseSchemeUnsupported s) -> + apiError err501 WrongEncryptionPassphrase $ mconcat + [ "This build is not compiled with support for the " + , toText s <> " scheme used by the given wallet: " + , toText wid + ] instance IsServerError ErrListAssets where toServerError = \case diff --git a/lib/core/src/Cardano/Wallet/Api/Types.hs b/lib/core/src/Cardano/Wallet/Api/Types.hs index 79daf3cd64e..8d105343870 100644 --- a/lib/core/src/Cardano/Wallet/Api/Types.hs +++ b/lib/core/src/Cardano/Wallet/Api/Types.hs @@ -273,9 +273,6 @@ import Cardano.Wallet.Primitive.AddressDerivation , DerivationType (..) , Index (..) , NetworkDiscriminant (..) - , Passphrase (..) - , PassphraseMaxLength (..) - , PassphraseMinLength (..) , Role (..) , fromHex , hex @@ -286,6 +283,12 @@ import Cardano.Wallet.Primitive.AddressDiscovery.Random ( RndState ) import Cardano.Wallet.Primitive.AddressDiscovery.Sequential ( AddressPoolGap, SeqState, getAddressPoolGap, purposeCIP1852 ) +import Cardano.Wallet.Primitive.Passphrase.Types + ( Passphrase (..) + , PassphraseHash (..) + , PassphraseMaxLength (..) + , PassphraseMinLength (..) + ) import Cardano.Wallet.Primitive.Slotting ( Qry, timeOfEpoch ) import Cardano.Wallet.Primitive.SyncProgress @@ -841,7 +844,7 @@ data WalletPostData = WalletPostData , mnemonicSentence :: !(ApiMnemonicT (AllowedMnemonics 'Shelley)) , mnemonicSecondFactor :: !(Maybe (ApiMnemonicT (AllowedMnemonics 'SndFactor))) , name :: !(ApiT WalletName) - , passphrase :: !(ApiT (Passphrase "raw")) + , passphrase :: !(ApiT (Passphrase "user")) } deriving (Eq, Generic, Show) data SomeByronWalletPostData @@ -856,7 +859,7 @@ data SomeByronWalletPostData data ByronWalletPostData mw = ByronWalletPostData { mnemonicSentence :: !(ApiMnemonicT mw) , name :: !(ApiT WalletName) - , passphrase :: !(ApiT (Passphrase "raw")) + , passphrase :: !(ApiT (Passphrase "user")) } deriving (Eq, Generic, Show) data ByronWalletFromXPrvPostData = ByronWalletFromXPrvPostData @@ -864,7 +867,7 @@ data ByronWalletFromXPrvPostData = ByronWalletFromXPrvPostData , encryptedRootPrivateKey :: !(ApiT XPrv) -- ^ A root private key hex-encoded, encrypted using a given passphrase. -- The underlying key should contain: private key, chain code, and public key - , passphraseHash :: !(ApiT (Hash "encryption")) + , passphraseHash :: !(ApiT PassphraseHash) -- ^ A hash of master passphrase. The hash should be an output of a -- Scrypt function with the following parameters: -- - logN = 14 @@ -905,13 +908,13 @@ newtype SettingsPutData = SettingsPutData deriving Show via (Quiet SettingsPutData) data WalletPutPassphraseData = WalletPutPassphraseData - { oldPassphrase :: !(ApiT (Passphrase "raw")) - , newPassphrase :: !(ApiT (Passphrase "raw")) + { oldPassphrase :: !(ApiT (Passphrase "user")) + , newPassphrase :: !(ApiT (Passphrase "user")) } deriving (Eq, Generic, Show) data ByronWalletPutPassphraseData = ByronWalletPutPassphraseData { oldPassphrase :: !(Maybe (ApiT (Passphrase "lenient"))) - , newPassphrase :: !(ApiT (Passphrase "raw")) + , newPassphrase :: !(ApiT (Passphrase "user")) } deriving (Eq, Generic, Show) data ApiConstructTransaction (n :: NetworkDiscriminant) = ApiConstructTransaction @@ -1220,7 +1223,7 @@ data ApiAnyCertificate n = deriving anyclass NFData newtype ApiPostPolicyKeyData = ApiPostPolicyKeyData - { passphrase :: ApiT (Passphrase "raw") + { passphrase :: ApiT (Passphrase "user") } deriving (Eq, Generic, Show) deriving anyclass NFData @@ -1509,13 +1512,13 @@ instance FromHttpApiData KeyFormat where parseUrlPiece = first (T.pack . getTextDecodingError) . fromText data ApiPostAccountKeyData = ApiPostAccountKeyData - { passphrase :: ApiT (Passphrase "raw") + { passphrase :: ApiT (Passphrase "user") , format :: KeyFormat } deriving (Eq, Generic, Show) deriving anyclass NFData data ApiPostAccountKeyDataWithPurpose = ApiPostAccountKeyDataWithPurpose - { passphrase :: ApiT (Passphrase "raw") + { passphrase :: ApiT (Passphrase "user") , format :: KeyFormat , purpose :: Maybe (ApiT DerivationIndex) } deriving (Eq, Generic, Show) @@ -1549,7 +1552,7 @@ data ApiSharedWalletPostDataFromMnemonics = ApiSharedWalletPostDataFromMnemonics { name :: !(ApiT WalletName) , mnemonicSentence :: !(ApiMnemonicT (AllowedMnemonics 'Shelley)) , mnemonicSecondFactor :: !(Maybe (ApiMnemonicT (AllowedMnemonics 'SndFactor))) - , passphrase :: !(ApiT (Passphrase "raw")) + , passphrase :: !(ApiT (Passphrase "user")) , accountIndex :: !(ApiT DerivationIndex) , paymentScriptTemplate :: !ApiScriptTemplateEntry , delegationScriptTemplate :: !(Maybe ApiScriptTemplateEntry) @@ -1801,9 +1804,9 @@ instance ToText (ApiT XPrv) where . CC.unXPrv . getApiT -instance FromText (ApiT (Hash "encryption")) where +instance FromText (ApiT PassphraseHash) where fromText txt = case convertFromBase Base16 $ T.encodeUtf8 txt of - Right bytes -> Right $ ApiT $ Hash bytes + Right bytes -> Right $ ApiT $ PassphraseHash bytes Left _ -> textDecodingError where textDecodingError = Left $ TextDecodingError $ unwords @@ -2516,9 +2519,9 @@ instance MkSomeMnemonic mw => FromJSON (ByronWalletPostData mw) where instance ToJSON (ByronWalletPostData mw) where toJSON = genericToJSON defaultRecordTypeOptions -instance FromJSON (ApiT (Hash "encryption")) where +instance FromJSON (ApiT PassphraseHash) where parseJSON = parseJSON >=> eitherToParser . first ShowFmt . fromText -instance ToJSON (ApiT (Hash "encryption")) where +instance ToJSON (ApiT PassphraseHash) where toJSON = toTextJSON instance FromJSON (ApiT XPrv) where diff --git a/lib/core/src/Cardano/Wallet/DB.hs b/lib/core/src/Cardano/Wallet/DB.hs index e9879d8b204..2d05769b379 100644 --- a/lib/core/src/Cardano/Wallet/DB.hs +++ b/lib/core/src/Cardano/Wallet/DB.hs @@ -44,6 +44,8 @@ import Cardano.Wallet.Primitive.AddressDerivation ( Depth (..) ) import Cardano.Wallet.Primitive.Model ( Wallet ) +import Cardano.Wallet.Primitive.Passphrase + ( PassphraseHash ) import Cardano.Wallet.Primitive.Types ( ChainPoint , DelegationCertificate @@ -298,7 +300,7 @@ data DBLayer m s k = forall stm. (MonadIO stm, MonadFail stm) => DBLayer , putPrivateKey :: WalletId - -> (k 'RootK XPrv, Hash "encryption") + -> (k 'RootK XPrv, PassphraseHash) -> ExceptT ErrNoSuchWallet stm () -- ^ Store or replace a private key for a given wallet. Note that wallet -- _could_ be stored and manipulated without any private key associated @@ -307,7 +309,7 @@ data DBLayer m s k = forall stm. (MonadIO stm, MonadFail stm) => DBLayer , readPrivateKey :: WalletId - -> stm (Maybe (k 'RootK XPrv, Hash "encryption")) + -> stm (Maybe (k 'RootK XPrv, PassphraseHash)) -- ^ Read a previously stored private key and its associated passphrase -- hash. diff --git a/lib/core/src/Cardano/Wallet/DB/MVar.hs b/lib/core/src/Cardano/Wallet/DB/MVar.hs index 19136e13993..91c4d21fb98 100644 --- a/lib/core/src/Cardano/Wallet/DB/MVar.hs +++ b/lib/core/src/Cardano/Wallet/DB/MVar.hs @@ -60,12 +60,12 @@ import Cardano.Wallet.DB.Model ) import Cardano.Wallet.Primitive.AddressDerivation ( Depth (..) ) +import Cardano.Wallet.Primitive.Passphrase + ( PassphraseHash ) import Cardano.Wallet.Primitive.Slotting ( TimeInterpreter ) import Cardano.Wallet.Primitive.Types ( SortOrder (..), WalletId, wholeRange ) -import Cardano.Wallet.Primitive.Types.Hash - ( Hash ) import Cardano.Wallet.Primitive.Types.Tx ( TransactionInfo (..) ) import Control.Monad.IO.Unlift @@ -89,7 +89,7 @@ newDBLayer -> m (DBLayer m s k) newDBLayer timeInterpreter = do lock <- newMVar () - db <- newMVar (emptyDatabase :: Database WalletId s (k 'RootK XPrv, Hash "encryption")) + db <- newMVar (emptyDatabase :: Database WalletId s (k 'RootK XPrv, PassphraseHash)) return $ DBLayer {----------------------------------------------------------------------- diff --git a/lib/core/src/Cardano/Wallet/DB/Sqlite.hs b/lib/core/src/Cardano/Wallet/DB/Sqlite.hs index 56e8aa78021..3cd4ab16f3d 100644 --- a/lib/core/src/Cardano/Wallet/DB/Sqlite.hs +++ b/lib/core/src/Cardano/Wallet/DB/Sqlite.hs @@ -123,7 +123,30 @@ import Cardano.Wallet.DB.WalletState , getSlot ) import Cardano.Wallet.Primitive.AddressDerivation - ( Depth (..), PersistPrivateKey (..), WalletKey (..) ) + ( Depth (..) + , DerivationType (..) + , Index (..) + , MkKeyFingerprint (..) + , NetworkDiscriminant (..) + , PaymentAddress (..) + , PersistPrivateKey (..) + , PersistPublicKey (..) + , Role (..) + , SoftDerivation (..) + , WalletKey (..) + ) +import Cardano.Wallet.Primitive.AddressDerivation.Icarus + ( IcarusKey ) +import Cardano.Wallet.Primitive.AddressDerivation.SharedKey + ( SharedKey (..) ) +import Cardano.Wallet.Primitive.AddressDerivation.Shelley + ( ShelleyKey (..) ) +import Cardano.Wallet.Primitive.AddressDiscovery + ( GetPurpose ) +import Cardano.Wallet.Primitive.AddressDiscovery.Shared + ( CredentialType (..) ) +import Cardano.Wallet.Primitive.Passphrase + ( PassphraseHash ) import Cardano.Wallet.Primitive.Slotting ( TimeInterpreter , epochOf @@ -217,6 +240,7 @@ import UnliftIO.MVar ( modifyMVar, modifyMVar_, newMVar, readMVar, withMVar ) import qualified Cardano.Wallet.Primitive.Model as W +import qualified Cardano.Wallet.Primitive.Passphrase as W import qualified Cardano.Wallet.Primitive.Types as W import qualified Cardano.Wallet.Primitive.Types.Coin as Coin import qualified Cardano.Wallet.Primitive.Types.Coin as W @@ -926,7 +950,7 @@ metadataFromEntity walDelegation wal = W.WalletMetadata mkPrivateKeyEntity :: PersistPrivateKey (k 'RootK) => W.WalletId - -> (k 'RootK XPrv, W.Hash "encryption") + -> (k 'RootK XPrv, W.PassphraseHash) -> PrivateKey mkPrivateKeyEntity wid kh = PrivateKey { privateKeyWalletId = wid @@ -939,7 +963,7 @@ mkPrivateKeyEntity wid kh = PrivateKey privateKeyFromEntity :: PersistPrivateKey (k 'RootK) => PrivateKey - -> (k 'RootK XPrv, W.Hash "encryption") + -> (k 'RootK XPrv, PassphraseHash) privateKeyFromEntity (PrivateKey _ k h) = unsafeDeserializeXPrv (k, h) @@ -1516,7 +1540,7 @@ updatePendingTxForExpiryQuery wid tip = do selectPrivateKey :: (MonadIO m, PersistPrivateKey (k 'RootK)) => W.WalletId - -> SqlPersistT m (Maybe (k 'RootK XPrv, W.Hash "encryption")) + -> SqlPersistT m (Maybe (k 'RootK XPrv, PassphraseHash)) selectPrivateKey wid = do keys <- selectFirst [PrivateKeyWalletId ==. wid] [] pure $ (privateKeyFromEntity . entityVal) <$> keys diff --git a/lib/core/src/Cardano/Wallet/DB/Sqlite/TH.hs b/lib/core/src/Cardano/Wallet/DB/Sqlite/TH.hs index d8218912216..9358786f856 100644 --- a/lib/core/src/Cardano/Wallet/DB/Sqlite/TH.hs +++ b/lib/core/src/Cardano/Wallet/DB/Sqlite/TH.hs @@ -52,6 +52,7 @@ import System.Random import qualified Cardano.Wallet.Primitive.AddressDerivation as W import qualified Cardano.Wallet.Primitive.AddressDiscovery.Sequential as W +import qualified Cardano.Wallet.Primitive.Passphrase.Types as W import qualified Cardano.Wallet.Primitive.Types as W import qualified Cardano.Wallet.Primitive.Types.Address as W import qualified Cardano.Wallet.Primitive.Types.Coin as W diff --git a/lib/core/src/Cardano/Wallet/DB/Sqlite/Types.hs b/lib/core/src/Cardano/Wallet/DB/Sqlite/Types.hs index 8eb7e750d4f..9a936ba6512 100644 --- a/lib/core/src/Cardano/Wallet/DB/Sqlite/Types.hs +++ b/lib/core/src/Cardano/Wallet/DB/Sqlite/Types.hs @@ -35,7 +35,7 @@ import Cardano.Api import Cardano.Slotting.Slot ( SlotNo (..) ) import Cardano.Wallet.Primitive.AddressDerivation - ( Passphrase (..), PassphraseScheme (..), Role (..) ) + ( Role (..) ) import Cardano.Wallet.Primitive.AddressDiscovery.Sequential ( AddressPoolGap (..) , DerivationPrefix @@ -44,6 +44,8 @@ import Cardano.Wallet.Primitive.AddressDiscovery.Sequential ) import Cardano.Wallet.Primitive.AddressDiscovery.Shared ( CredentialType ) +import Cardano.Wallet.Primitive.Passphrase.Types + ( Passphrase (..), PassphraseScheme (..) ) import Cardano.Wallet.Primitive.Types ( EpochNo (..) , FeePolicy diff --git a/lib/core/src/Cardano/Wallet/Primitive/AddressDerivation.hs b/lib/core/src/Cardano/Wallet/Primitive/AddressDerivation.hs index d75a705ea40..f52446969ff 100644 --- a/lib/core/src/Cardano/Wallet/Primitive/AddressDerivation.hs +++ b/lib/core/src/Cardano/Wallet/Primitive/AddressDerivation.hs @@ -71,16 +71,6 @@ module Cardano.Wallet.Primitive.AddressDerivation , MkKeyFingerprint(..) , ErrMkKeyFingerprint(..) , KeyFingerprint(..) - - -- * Passphrase - , Passphrase(..) - , PassphraseMinLength(..) - , PassphraseMaxLength(..) - , ErrWrongPassphrase(..) - , PassphraseScheme(..) - , encryptPassphrase - , checkPassphrase - , preparePassphrase ) where import Prelude @@ -88,37 +78,29 @@ import Prelude import Cardano.Address.Derivation ( XPrv, XPub, xpubPublicKey ) import Cardano.Address.Script - ( KeyHash (KeyHash), KeyRole ) + ( KeyHash (..), KeyRole ) import Cardano.Mnemonic ( SomeMnemonic ) -import Cardano.Wallet.Primitive.Types - ( PassphraseScheme (..) ) +import Cardano.Wallet.Primitive.Passphrase.Types + ( Passphrase (..), PassphraseHash (..), PassphraseScheme ) import Cardano.Wallet.Primitive.Types.Address ( Address (..) ) -import Cardano.Wallet.Primitive.Types.Hash - ( Hash (..) ) import Cardano.Wallet.Primitive.Types.RewardAccount ( RewardAccount (..) ) import Control.DeepSeq ( NFData ) import Control.Monad - ( unless, (>=>) ) + ( (>=>) ) import Crypto.Hash ( Digest, HashAlgorithm ) import Crypto.Hash.Utils - ( blake2b224, blake2b256 ) -import Crypto.KDF.PBKDF2 - ( Parameters (..), fastPBKDF2_SHA512 ) -import Crypto.Random.Types - ( MonadRandom (..) ) + ( blake2b224 ) import Data.ByteArray - ( ByteArray, ByteArrayAccess, ScrubbedBytes ) + ( ByteArray, ByteArrayAccess ) import Data.ByteArray.Encoding ( Base (..), convertFromBase, convertToBase ) import Data.ByteString ( ByteString ) -import Data.Coerce - ( coerce ) import Data.Kind ( Type ) import Data.List.NonEmpty @@ -154,13 +136,7 @@ import Quiet import Safe ( readMay, toEnumMay ) -import qualified Codec.CBOR.Encoding as CBOR -import qualified Codec.CBOR.Write as CBOR -import qualified Crypto.Scrypt as Scrypt -import qualified Data.ByteArray as BA -import qualified Data.ByteString as BS import qualified Data.Text as T -import qualified Data.Text.Encoding as T {------------------------------------------------------------------------------- HD Hierarchy @@ -520,144 +496,6 @@ hashVerificationKey hashVerificationKey keyRole = KeyHash keyRole . blake2b224 . xpubPublicKey . getRawKey -{------------------------------------------------------------------------------- - Passphrases --------------------------------------------------------------------------------} - --- | An encapsulated passphrase. The inner format is free, but the wrapper helps --- readability in function signatures. -newtype Passphrase (purpose :: Symbol) = Passphrase ScrubbedBytes - deriving stock (Eq, Show) - deriving newtype (Semigroup, Monoid, NFData, ByteArrayAccess) - -type role Passphrase phantom - -class PassphraseMinLength (purpose :: Symbol) where - -- | Minimal Length for a passphrase, for lack of better validations - passphraseMinLength :: Proxy purpose -> Int - -class PassphraseMaxLength (purpose :: Symbol) where - -- | Maximum length for a passphrase - passphraseMaxLength :: Proxy purpose -> Int - -instance PassphraseMinLength "raw" where passphraseMinLength _ = 10 -instance PassphraseMaxLength "raw" where passphraseMaxLength _ = 255 - -instance PassphraseMinLength "lenient" where passphraseMinLength _ = 0 -instance PassphraseMaxLength "lenient" where passphraseMaxLength _ = 255 - -instance - ( PassphraseMaxLength purpose - , PassphraseMinLength purpose - ) => FromText (Passphrase purpose) where - fromText t - | T.length t < minLength = - Left $ TextDecodingError $ - "passphrase is too short: expected at least " - <> show minLength <> " characters" - | T.length t > maxLength = - Left $ TextDecodingError $ - "passphrase is too long: expected at most " - <> show maxLength <> " characters" - | otherwise = - pure $ Passphrase $ BA.convert $ T.encodeUtf8 t - where - minLength = passphraseMinLength (Proxy :: Proxy purpose) - maxLength = passphraseMaxLength (Proxy :: Proxy purpose) - -instance ToText (Passphrase purpose) where - toText (Passphrase bytes) = T.decodeUtf8 $ BA.convert bytes - --- | Encrypt a 'Passphrase' into a format that is suitable for storing on disk -encryptPassphrase - :: MonadRandom m - => Passphrase "encryption" - -> m (Hash "encryption") -encryptPassphrase (Passphrase bytes) = do - salt <- getRandomBytes @_ @ByteString 16 - let params = Parameters - { iterCounts = 20000 - , outputLength = 64 - } - return $ Hash $ BA.convert $ mempty - <> BS.singleton (fromIntegral (BS.length salt)) - <> salt - <> BA.convert @ByteString (fastPBKDF2_SHA512 params bytes salt) - --- | Manipulation done on legacy passphrases before getting encrypted. -preparePassphrase - :: PassphraseScheme - -> Passphrase "raw" - -> Passphrase "encryption" -preparePassphrase = \case - EncryptWithPBKDF2 -> coerce - EncryptWithScrypt -> Passphrase . hashMaybe - where - hashMaybe pw@(Passphrase bytes) - | pw == mempty = BA.convert bytes - | otherwise = BA.convert $ blake2b256 bytes - --- | Check whether a 'Passphrase' matches with a stored 'Hash' -checkPassphrase - :: PassphraseScheme - -> Passphrase "raw" - -> Hash "encryption" - -> Either ErrWrongPassphrase () -checkPassphrase scheme received stored = do - let prepared = preparePassphrase scheme received - case scheme of - EncryptWithPBKDF2 -> do - salt <- getSalt stored - unless (constantTimeEq (encryptPassphrase prepared salt) stored) $ - Left ErrWrongPassphrase - EncryptWithScrypt -> do - let msg = Scrypt.Pass - $ CBOR.toStrictByteString - $ CBOR.encodeBytes - $ BA.convert prepared - if Scrypt.verifyPass' msg (Scrypt.EncryptedPass (getHash stored)) - then Right () - else Left ErrWrongPassphrase - where - getSalt :: Hash purpose -> Either ErrWrongPassphrase (Passphrase "salt") - getSalt (Hash bytes) = do - len <- case BS.unpack (BS.take 1 bytes) of - [len] -> Right $ fromIntegral len - _ -> Left ErrWrongPassphrase - Right $ Passphrase $ BA.convert $ BS.take len (BS.drop 1 bytes) - constantTimeEq :: Hash purpose -> Hash purpose -> Bool - constantTimeEq (Hash a) (Hash b) = - BA.convert @_ @ScrubbedBytes a == BA.convert @_ @ScrubbedBytes b - --- | Indicate a failure when checking for a given 'Passphrase' match -data ErrWrongPassphrase = ErrWrongPassphrase - deriving stock (Show, Eq) - --- | Little trick to be able to provide our own "random" salt in order to --- deterministically re-compute a passphrase hash from a known salt. Note that, --- this boils down to giving an extra argument to the `encryptPassphrase` --- function which is the salt, in order to make it behave deterministically. --- --- @ --- encryptPassphrase --- :: MonadRandom m --- => Passphrase purpose --- -> m (Hash purpose) --- --- ~ --- --- encryptPassphrase --- :: Passphrase purpose --- -> Passphrase "salt" --- -> m (Hash purpose) --- @ --- --- >>> encryptPassphrase pwd (Passphrase @"salt" salt) --- Hash "..." --- -instance MonadRandom ((->) (Passphrase "salt")) where - getRandomBytes _ (Passphrase salt) = BA.convert salt - {------------------------------------------------------------------------------- Network Discrimination -------------------------------------------------------------------------------} @@ -707,9 +545,9 @@ class WalletKey (key :: Depth -> Type -> Type) where -- expected to have already checked that. Using an incorrect passphrase here -- will lead to very bad thing. changePassphrase - :: Passphrase "encryption" + :: (PassphraseScheme, Passphrase "user") -- ^ Old passphrase - -> Passphrase "encryption" + -> (PassphraseScheme, Passphrase "user") -- ^ New passphrase -> key depth XPrv -> key depth XPrv @@ -795,14 +633,14 @@ class PersistPrivateKey (key :: Type -> Type) where -- | Convert a private key and its password hash into hexadecimal strings -- suitable for storing in a text file or database column. serializeXPrv - :: (key XPrv, Hash "encryption") + :: (key XPrv, PassphraseHash) -> (ByteString, ByteString) -- | The reverse of 'serializeXPrv'. This may fail if the inputs are not -- valid hexadecimal strings, or if the key is of the wrong length. unsafeDeserializeXPrv :: (ByteString, ByteString) - -> (key XPrv, Hash "encryption") + -> (key XPrv, PassphraseHash) -- | Operations for saving a public key into a database, and restoring it from -- a database. The keys should be encoded in hexadecimal strings. diff --git a/lib/core/src/Cardano/Wallet/Primitive/AddressDerivation/Byron.hs b/lib/core/src/Cardano/Wallet/Primitive/AddressDerivation/Byron.hs index 41b067e7a80..65e79eea6c3 100644 --- a/lib/core/src/Cardano/Wallet/Primitive/AddressDerivation/Byron.hs +++ b/lib/core/src/Cardano/Wallet/Primitive/AddressDerivation/Byron.hs @@ -54,7 +54,6 @@ import Cardano.Crypto.Wallet , toXPub , unXPrv , unXPub - , xPrvChangePass , xprv ) import Cardano.Mnemonic @@ -67,19 +66,22 @@ import Cardano.Wallet.Primitive.AddressDerivation , KeyFingerprint (..) , MkKeyFingerprint (..) , NetworkDiscriminant (..) - , Passphrase (..) , PaymentAddress (..) , PersistPrivateKey (..) , WalletKey (..) , fromHex , hex ) +import Cardano.Wallet.Primitive.Passphrase + ( Passphrase (..) + , PassphraseHash (..) + , PassphraseScheme (..) + , changePassphraseXPrv + ) import Cardano.Wallet.Primitive.Types ( testnetMagic ) import Cardano.Wallet.Primitive.Types.Address ( Address (..) ) -import Cardano.Wallet.Primitive.Types.Hash - ( Hash (..) ) import Cardano.Wallet.Util ( invariant ) import Control.DeepSeq @@ -279,17 +281,17 @@ unsafeMkByronKeyFromMasterKey derivationPath masterKey = ByronKey -- expected to have already checked that. Using an incorrect passphrase here -- will lead to very bad thing. changePassphraseRnd - :: Passphrase "encryption" - -> Passphrase "encryption" + :: (PassphraseScheme, Passphrase "user") + -> (PassphraseScheme, Passphrase "user") -> ByronKey depth XPrv -> ByronKey depth XPrv -changePassphraseRnd (Passphrase oldPwd) (Passphrase newPwd) key = ByronKey +changePassphraseRnd old new key = ByronKey { getKey = masterKey , derivationPath = derivationPath key , payloadPassphrase = hdPassphrase (toXPub masterKey) } where - masterKey = xPrvChangePass oldPwd newPwd (getKey key) + masterKey = changePassphraseXPrv old new (getKey key) {------------------------------------------------------------------------------- HD derivation @@ -355,12 +357,12 @@ deriveAddressPrivateKey (Passphrase pwd) accountKey idx@(Index addrIx) = ByronKe instance PersistPrivateKey (ByronKey 'RootK) where serializeXPrv ((ByronKey k _ (Passphrase p)), h) = ( hex (unXPrv k) <> ":" <> hex p - , hex . getHash $ h + , hex . getPassphraseHash $ h ) unsafeDeserializeXPrv (k, h) = either err id $ (,) <$> fmap mkKey (deserializeKey k) - <*> fmap Hash (fromHex h) + <*> fmap PassphraseHash (fromHex h) where err _ = error "unsafeDeserializeXPrv: unable to deserialize ByronKey" mkKey (key, pwd) = ByronKey key () pwd diff --git a/lib/core/src/Cardano/Wallet/Primitive/AddressDerivation/Icarus.hs b/lib/core/src/Cardano/Wallet/Primitive/AddressDerivation/Icarus.hs index 7a7b45edd23..3664d1980d3 100644 --- a/lib/core/src/Cardano/Wallet/Primitive/AddressDerivation/Icarus.hs +++ b/lib/core/src/Cardano/Wallet/Primitive/AddressDerivation/Icarus.hs @@ -56,7 +56,6 @@ import Cardano.Wallet.Primitive.AddressDerivation , KeyFingerprint (..) , MkKeyFingerprint (..) , NetworkDiscriminant (..) - , Passphrase (..) , PaymentAddress (..) , PersistPrivateKey (..) , PersistPublicKey (..) @@ -70,12 +69,12 @@ import Cardano.Wallet.Primitive.AddressDiscovery ( GetPurpose (..), IsOurs (..) ) import Cardano.Wallet.Primitive.AddressDiscovery.Sequential ( SeqState, coinTypeAda, purposeBIP44 ) +import Cardano.Wallet.Primitive.Passphrase + ( Passphrase (..), PassphraseHash (..), changePassphraseXPrv ) import Cardano.Wallet.Primitive.Types ( testnetMagic ) import Cardano.Wallet.Primitive.Types.Address ( Address (..) ) -import Cardano.Wallet.Primitive.Types.Hash - ( Hash (..) ) import Cardano.Wallet.Util ( invariant ) import Control.Arrow @@ -344,8 +343,8 @@ instance SoftDerivation IcarusKey where instance WalletKey IcarusKey where keyTypeDescriptor _ = "ica" - changePassphrase (Passphrase old) (Passphrase new) (IcarusKey prv) = - IcarusKey $ xPrvChangePass old new prv + changePassphrase old new (IcarusKey prv) = + IcarusKey $ changePassphraseXPrv old new prv publicKey (IcarusKey prv) = IcarusKey (toXPub prv) @@ -409,12 +408,12 @@ instance IsOurs (SeqState n IcarusKey) RewardAccount where instance PersistPrivateKey (IcarusKey 'RootK) where serializeXPrv (k, h) = ( hex . unXPrv . getKey $ k - , hex . getHash $ h + , hex . getPassphraseHash $ h ) unsafeDeserializeXPrv (k, h) = either err id $ (,) <$> fmap IcarusKey (xprvFromText k) - <*> fmap Hash (fromHex h) + <*> fmap PassphraseHash (fromHex h) where xprvFromText = xprv <=< fromHex @ByteString err _ = error "unsafeDeserializeXPrv: unable to deserialize IcarusKey" diff --git a/lib/core/src/Cardano/Wallet/Primitive/AddressDerivation/MintBurn.hs b/lib/core/src/Cardano/Wallet/Primitive/AddressDerivation/MintBurn.hs index 20af0f2dd3f..32a575dd385 100644 --- a/lib/core/src/Cardano/Wallet/Primitive/AddressDerivation/MintBurn.hs +++ b/lib/core/src/Cardano/Wallet/Primitive/AddressDerivation/MintBurn.hs @@ -45,7 +45,6 @@ import Cardano.Wallet.Primitive.AddressDerivation , DerivationIndex (..) , DerivationType (..) , Index (..) - , Passphrase (..) , WalletKey , getIndex , getRawKey @@ -55,6 +54,8 @@ import Cardano.Wallet.Primitive.AddressDerivation ) import Cardano.Wallet.Primitive.AddressDiscovery ( coinTypeAda ) +import Cardano.Wallet.Primitive.Passphrase + ( Passphrase (..) ) import Cardano.Wallet.Primitive.Types.Hash ( Hash (..) ) import Cardano.Wallet.Primitive.Types.TokenMap diff --git a/lib/core/src/Cardano/Wallet/Primitive/AddressDerivation/Shared.hs b/lib/core/src/Cardano/Wallet/Primitive/AddressDerivation/Shared.hs index 466372a163f..ab41d2ab90f 100644 --- a/lib/core/src/Cardano/Wallet/Primitive/AddressDerivation/Shared.hs +++ b/lib/core/src/Cardano/Wallet/Primitive/AddressDerivation/Shared.hs @@ -34,7 +34,7 @@ import Prelude import Cardano.Address.Derivation ( xpubPublicKey ) import Cardano.Crypto.Wallet - ( XPrv, XPub, toXPub, unXPrv, unXPub, xPrvChangePass, xprv, xpub ) + ( XPrv, XPub, toXPub, unXPrv, unXPub, xprv, xpub ) import Cardano.Mnemonic ( SomeMnemonic ) import Cardano.Wallet.Primitive.AddressDerivation @@ -44,7 +44,6 @@ import Cardano.Wallet.Primitive.AddressDerivation , KeyFingerprint (..) , MkKeyFingerprint (..) , NetworkDiscriminant (..) - , Passphrase (..) , PersistPrivateKey (..) , PersistPublicKey (..) , SoftDerivation (..) @@ -62,10 +61,10 @@ import Cardano.Wallet.Primitive.AddressDerivation.Shelley ) import Cardano.Wallet.Primitive.AddressDiscovery ( GetPurpose (..) ) +import Cardano.Wallet.Primitive.Passphrase + ( Passphrase (..), PassphraseHash (..), changePassphraseXPrv ) import Cardano.Wallet.Primitive.Types.Address ( Address (..) ) -import Cardano.Wallet.Primitive.Types.Hash - ( Hash (..) ) import Control.Monad ( (<=<) ) import Crypto.Hash @@ -126,8 +125,8 @@ instance SoftDerivation SharedKey where -------------------------------------------------------------------------------} instance WalletKey SharedKey where - changePassphrase (Passphrase oldPwd) (Passphrase newPwd) (SharedKey prv) = - SharedKey $ xPrvChangePass oldPwd newPwd prv + changePassphrase oldPwd newPwd (SharedKey prv) = + SharedKey $ changePassphraseXPrv oldPwd newPwd prv publicKey (SharedKey prv) = SharedKey (toXPub prv) @@ -158,12 +157,12 @@ instance GetPurpose SharedKey where instance PersistPrivateKey (SharedKey 'RootK) where serializeXPrv (k, h) = ( hex . unXPrv . getKey $ k - , hex . getHash $ h + , hex . getPassphraseHash $ h ) unsafeDeserializeXPrv (k, h) = either err id $ (,) <$> fmap SharedKey (xprvFromText k) - <*> fmap Hash (fromHex h) + <*> fmap PassphraseHash (fromHex h) where xprvFromText = xprv <=< fromHex @ByteString err _ = error "unsafeDeserializeXPrv: unable to deserialize SharedKey" @@ -190,5 +189,4 @@ instance MkKeyFingerprint SharedKey (Proxy (n :: NetworkDiscriminant), SharedKey -------------------------------------------------------------------------------} hashSize :: Int -hashSize = - hashDigestSize Blake2b_224 +hashSize = hashDigestSize Blake2b_224 diff --git a/lib/core/src/Cardano/Wallet/Primitive/AddressDerivation/Shelley.hs b/lib/core/src/Cardano/Wallet/Primitive/AddressDerivation/Shelley.hs index fb2956a9048..69ed23260c5 100644 --- a/lib/core/src/Cardano/Wallet/Primitive/AddressDerivation/Shelley.hs +++ b/lib/core/src/Cardano/Wallet/Primitive/AddressDerivation/Shelley.hs @@ -49,7 +49,6 @@ import Cardano.Crypto.Wallet , toXPub , unXPrv , unXPub - , xPrvChangePass , xprv , xpub ) @@ -65,7 +64,6 @@ import Cardano.Wallet.Primitive.AddressDerivation , KeyFingerprint (..) , MkKeyFingerprint (..) , NetworkDiscriminant (..) - , Passphrase (..) , PaymentAddress (..) , PersistPrivateKey (..) , PersistPublicKey (..) @@ -88,10 +86,10 @@ import Cardano.Wallet.Primitive.AddressDiscovery.Sequential , purposeCIP1852 , rewardAccountKey ) +import Cardano.Wallet.Primitive.Passphrase + ( Passphrase (..), PassphraseHash (..), changePassphraseXPrv ) import Cardano.Wallet.Primitive.Types.Address ( Address (..) ) -import Cardano.Wallet.Primitive.Types.Hash - ( Hash (..) ) import Cardano.Wallet.Util ( invariant ) import Control.DeepSeq @@ -171,8 +169,8 @@ unsafeGenerateKeyFromSeedShelley -- ^ The actual seed and its recovery / generation passphrase -> Passphrase "encryption" -> XPrv -unsafeGenerateKeyFromSeedShelley (root, m2nd) (Passphrase pwd) = - generateNew seed' (maybe mempty mnemonicToBytes m2nd) pwd +unsafeGenerateKeyFromSeedShelley (root, m2nd) pwd = + generateNew seed' (maybe mempty mnemonicToBytes m2nd) (unPassphrase pwd) where mnemonicToBytes (SomeMnemonic mw) = entropyToBytes $ mnemonicToEntropy mw seed = mnemonicToBytes root @@ -183,7 +181,7 @@ unsafeGenerateKeyFromSeedShelley (root, m2nd) (Passphrase pwd) = deriveAccountPrivateKeyShelley :: Index 'Hardened 'PurposeK - -> Passphrase purpose + -> Passphrase "encryption" -> XPrv -> Index 'Hardened 'AccountK -> XPrv @@ -198,7 +196,7 @@ deriveAccountPrivateKeyShelley purpose (Passphrase pwd) rootXPrv (Index accIx) = deriveAddressPrivateKeyShelley :: Enum a - => Passphrase purpose + => Passphrase "encryption" -> XPrv -> a -> Index derivationType level @@ -250,8 +248,8 @@ instance SoftDerivation ShelleyKey where -------------------------------------------------------------------------------} instance WalletKey ShelleyKey where - changePassphrase (Passphrase oldPwd) (Passphrase newPwd) (ShelleyKey prv) = - ShelleyKey $ xPrvChangePass oldPwd newPwd prv + changePassphrase oldPwd newPwd (ShelleyKey prv) = + ShelleyKey $ changePassphraseXPrv oldPwd newPwd prv publicKey (ShelleyKey prv) = ShelleyKey (toXPub prv) @@ -406,12 +404,12 @@ toRewardAccountRaw = RewardAccount . blake2b224 . xpubPublicKey instance PersistPrivateKey (ShelleyKey 'RootK) where serializeXPrv (k, h) = ( hex . unXPrv . getKey $ k - , hex . getHash $ h + , hex . getPassphraseHash $ h ) unsafeDeserializeXPrv (k, h) = either err id $ (,) <$> fmap ShelleyKey (xprvFromText k) - <*> fmap Hash (fromHex h) + <*> fmap PassphraseHash (fromHex h) where xprvFromText = xprv <=< fromHex @ByteString err _ = error "unsafeDeserializeXPrv: unable to deserialize ShelleyKey" diff --git a/lib/core/src/Cardano/Wallet/Primitive/AddressDiscovery.hs b/lib/core/src/Cardano/Wallet/Primitive/AddressDiscovery.hs index fa38f20a8f1..4fc02c2a771 100644 --- a/lib/core/src/Cardano/Wallet/Primitive/AddressDiscovery.hs +++ b/lib/core/src/Cardano/Wallet/Primitive/AddressDiscovery.hs @@ -42,11 +42,12 @@ import Cardano.Wallet.Primitive.AddressDerivation , DerivationIndex (..) , DerivationType (..) , Index (..) - , Passphrase (..) , RewardAccount ) import Cardano.Wallet.Primitive.BlockSummary ( ChainEvents ) +import Cardano.Wallet.Primitive.Passphrase + ( Passphrase (..) ) import Cardano.Wallet.Primitive.Types.Address ( Address (..), AddressState (..) ) import Data.Kind @@ -179,7 +180,7 @@ type LightDiscoverTxs s = -- | Function that discovers transactions based on an address. newtype DiscoverTxs addr txs s = DiscoverTxs - { discoverTxs + { discoverTxs :: forall m. Monad m => (addr -> m txs) -> s -> m (txs, s) } diff --git a/lib/core/src/Cardano/Wallet/Primitive/AddressDiscovery/Random.hs b/lib/core/src/Cardano/Wallet/Primitive/AddressDiscovery/Random.hs index 27d9d86a975..1057821deb0 100644 --- a/lib/core/src/Cardano/Wallet/Primitive/AddressDiscovery/Random.hs +++ b/lib/core/src/Cardano/Wallet/Primitive/AddressDiscovery/Random.hs @@ -55,7 +55,6 @@ import Cardano.Wallet.Primitive.AddressDerivation , DerivationType (..) , Index (..) , NetworkDiscriminant - , Passphrase (..) , PaymentAddress (..) , liftIndex , publicKey @@ -70,6 +69,8 @@ import Cardano.Wallet.Primitive.AddressDiscovery , KnownAddresses (..) , MaybeLight (..) ) +import Cardano.Wallet.Primitive.Passphrase + ( Passphrase (..) ) import Cardano.Wallet.Primitive.Types.Address ( Address (..), AddressState (..) ) import Cardano.Wallet.Primitive.Types.RewardAccount diff --git a/lib/core/src/Cardano/Wallet/Primitive/AddressDiscovery/Sequential.hs b/lib/core/src/Cardano/Wallet/Primitive/AddressDiscovery/Sequential.hs index 448588acb7f..4845597effe 100644 --- a/lib/core/src/Cardano/Wallet/Primitive/AddressDiscovery/Sequential.hs +++ b/lib/core/src/Cardano/Wallet/Primitive/AddressDiscovery/Sequential.hs @@ -91,7 +91,6 @@ import Cardano.Wallet.Primitive.AddressDerivation , KeyFingerprint (..) , MkKeyFingerprint (..) , NetworkDiscriminant (..) - , Passphrase (..) , PaymentAddress (..) , PersistPublicKey (..) , Role (..) @@ -112,6 +111,8 @@ import Cardano.Wallet.Primitive.AddressDiscovery , MaybeLight (..) , coinTypeAda ) +import Cardano.Wallet.Primitive.Passphrase + ( Passphrase ) import Cardano.Wallet.Primitive.Types.Address ( Address (..), AddressState (..) ) import Cardano.Wallet.Primitive.Types.RewardAccount diff --git a/lib/core/src/Cardano/Wallet/Primitive/AddressDiscovery/Shared.hs b/lib/core/src/Cardano/Wallet/Primitive/AddressDiscovery/Shared.hs index 56bea64433d..988afc4831f 100644 --- a/lib/core/src/Cardano/Wallet/Primitive/AddressDiscovery/Shared.hs +++ b/lib/core/src/Cardano/Wallet/Primitive/AddressDiscovery/Shared.hs @@ -74,7 +74,6 @@ import Cardano.Wallet.Primitive.AddressDerivation , KeyFingerprint (..) , MkKeyFingerprint (..) , NetworkDiscriminant (..) - , Passphrase , PersistPublicKey (..) , SoftDerivation , WalletKey (..) @@ -97,6 +96,8 @@ import Cardano.Wallet.Primitive.AddressDiscovery ) import Cardano.Wallet.Primitive.AddressDiscovery.Sequential ( AddressPoolGap (..), unsafePaymentKeyFingerprint ) +import Cardano.Wallet.Primitive.Passphrase + ( Passphrase ) import Cardano.Wallet.Primitive.Types.Address ( Address (..) ) import Cardano.Wallet.Primitive.Types.RewardAccount @@ -510,7 +511,7 @@ decoratePath st ix = NE.fromList , DerivationIndex $ getIndex accIx , DerivationIndex $ getIndex utxoExternal , DerivationIndex $ getIndex ix - ] + ] where DerivationPrefix (purpose, coinType, accIx) = derivationPrefix st diff --git a/lib/core/src/Cardano/Wallet/Primitive/Passphrase.hs b/lib/core/src/Cardano/Wallet/Primitive/Passphrase.hs new file mode 100644 index 00000000000..ed8cf5ec6ed --- /dev/null +++ b/lib/core/src/Cardano/Wallet/Primitive/Passphrase.hs @@ -0,0 +1,129 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE TupleSections #-} +{-# LANGUAGE TypeApplications #-} + +-- | +-- Copyright: © 2018-2021 IOHK +-- License: Apache-2.0 +-- +-- Hashing of wallet passwords. +-- + +module Cardano.Wallet.Primitive.Passphrase + ( -- * Passphrases from the user + Passphrase (..) + , PassphraseMinLength (..) + , PassphraseMaxLength (..) + , validatePassphrase + + -- * Wallet passphrases stored as hashes + , PassphraseHash (..) + , PassphraseScheme (..) + , currentPassphraseScheme + , WalletPassphraseInfo (..) + + -- * Operations + , encryptPassphrase + , encryptPassphrase' + , checkPassphrase + , preparePassphrase + , changePassphraseXPrv + , checkAndChangePassphraseXPrv + , ErrWrongPassphrase (..) + ) where + +import Prelude + +import Cardano.Crypto.Wallet + ( XPrv, xPrvChangePass ) +import Cardano.Wallet.Primitive.Passphrase.Types +import Crypto.Random.Types + ( MonadRandom ) + +import qualified Cardano.Wallet.Primitive.Passphrase.Current as PBKDF2 +import qualified Cardano.Wallet.Primitive.Passphrase.Legacy as Scrypt + +currentPassphraseScheme :: PassphraseScheme +currentPassphraseScheme = EncryptWithPBKDF2 + +-- | Hashes a 'Passphrase' into a format that is suitable for storing on +-- disk. It will always use the current scheme: pbkdf2-hmac-sha512. +encryptPassphrase + :: MonadRandom m + => Passphrase "user" + -> m (PassphraseScheme, PassphraseHash) +encryptPassphrase = fmap (currentPassphraseScheme,) + . encryptPassphrase' currentPassphraseScheme + +encryptPassphrase' + :: MonadRandom m + => PassphraseScheme + -> Passphrase "user" + -> m PassphraseHash +encryptPassphrase' scheme = encrypt . preparePassphrase scheme + where + encrypt = case scheme of + EncryptWithPBKDF2 -> PBKDF2.encryptPassphrase + EncryptWithScrypt -> Scrypt.encryptPassphraseTestingOnly + +-- | Manipulation done on legacy passphrases before used for encryption. +preparePassphrase + :: PassphraseScheme + -> Passphrase "user" + -> Passphrase "encryption" +preparePassphrase = \case + EncryptWithPBKDF2 -> PBKDF2.preparePassphrase + EncryptWithScrypt -> Scrypt.preparePassphrase + +-- | Check whether a 'Passphrase' matches with a stored 'Hash' +checkPassphrase + :: PassphraseScheme + -> Passphrase "user" + -> PassphraseHash + -> Either ErrWrongPassphrase () +checkPassphrase scheme received stored = case scheme of + EncryptWithPBKDF2 -> PBKDF2.checkPassphrase prepared stored + EncryptWithScrypt -> case Scrypt.checkPassphrase prepared stored of + Just True -> Right () + Just False -> Left ErrWrongPassphrase + Nothing -> Left (ErrPassphraseSchemeUnsupported scheme) + where + prepared = preparePassphrase scheme received + +-- | Re-encrypts a wallet private key with a new passphrase. +-- +-- **Important**: +-- This function doesn't check that the old passphrase is correct! Caller is +-- expected to have already checked that. Using an incorrect passphrase here +-- will lead to very bad thing. +changePassphraseXPrv + :: (PassphraseScheme, Passphrase "user") + -- ^ Old passphrase + -> (PassphraseScheme, Passphrase "user") + -- ^ New passphrase + -> XPrv + -- ^ Key to re-encrypt + -> XPrv +changePassphraseXPrv (oldS, old) (newS, new) = xPrvChangePass oldP newP + where + oldP = preparePassphrase oldS old + newP = preparePassphrase newS new + +-- | Re-encrypts a wallet private key with a new passphrase. +checkAndChangePassphraseXPrv + :: MonadRandom m + => ((PassphraseScheme, PassphraseHash), Passphrase "user") + -- ^ Old passphrase + -> Passphrase "user" + -- ^ New passphrase + -> XPrv + -- ^ Key to re-encrypt + -> m (Either ErrWrongPassphrase ((PassphraseScheme, PassphraseHash), XPrv)) +checkAndChangePassphraseXPrv ((oldS, oldH), old) new key = + case checkPassphrase oldS old oldH of + Right () -> do + (newS, newH) <- encryptPassphrase new + let newKey = changePassphraseXPrv (oldS, old) (newS, new) key + pure $ Right ((newS, newH), newKey) + Left e -> pure $ Left e diff --git a/lib/core/src/Cardano/Wallet/Primitive/Passphrase/Current.hs b/lib/core/src/Cardano/Wallet/Primitive/Passphrase/Current.hs new file mode 100644 index 00000000000..57447db591a --- /dev/null +++ b/lib/core/src/Cardano/Wallet/Primitive/Passphrase/Current.hs @@ -0,0 +1,80 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE TypeApplications #-} + +-- | +-- Copyright: © 2018-2021 IOHK +-- License: Apache-2.0 +-- +-- Generating and verifying hashes of wallet passwords. +-- + +module Cardano.Wallet.Primitive.Passphrase.Current + ( encryptPassphrase + , checkPassphrase + , preparePassphrase + , genSalt + ) where + +import Prelude + +import Cardano.Wallet.Primitive.Passphrase.Types + ( ErrWrongPassphrase (..), Passphrase (..), PassphraseHash (..) ) +import Control.Monad + ( unless ) +import Crypto.KDF.PBKDF2 + ( Parameters (..), fastPBKDF2_SHA512 ) +import Crypto.Random.Types + ( MonadRandom (..) ) +import Data.ByteArray + ( ScrubbedBytes ) +import Data.ByteString + ( ByteString ) +import Data.Coerce + ( coerce ) +import Data.Function + ( on ) + +import qualified Data.ByteArray as BA +import qualified Data.ByteString as BS + +-- | Encrypt a 'Passphrase' into a format that is suitable for storing on disk +encryptPassphrase + :: MonadRandom m + => Passphrase "encryption" + -> m PassphraseHash +encryptPassphrase (Passphrase bytes) = mkPassphraseHash <$> genSalt + where + mkPassphraseHash (Passphrase salt) = PassphraseHash $ BA.convert $ mempty + <> BS.singleton (fromIntegral (BA.length salt)) + <> BA.convert salt + <> fastPBKDF2_SHA512 params bytes salt + + params = Parameters + { iterCounts = 20000 + , outputLength = 64 + } + +genSalt :: MonadRandom m => m (Passphrase "salt") +genSalt = Passphrase <$> getRandomBytes 16 + +preparePassphrase :: Passphrase "user" -> Passphrase "encryption" +preparePassphrase = coerce + +checkPassphrase + :: Passphrase "encryption" + -> PassphraseHash + -> Either ErrWrongPassphrase () +checkPassphrase prepared stored = do + salt <- getSalt (BA.convert stored) + unless (constantTimeEq (encryptPassphrase prepared salt) stored) $ + Left ErrWrongPassphrase + where + getSalt :: ByteString -> Either ErrWrongPassphrase (Passphrase "salt") + getSalt bytes = do + len <- case BS.unpack (BS.take 1 bytes) of + [len] -> Right $ fromIntegral len + _ -> Left ErrWrongPassphrase + Right $ Passphrase $ BA.convert $ BS.take len (BS.drop 1 bytes) + + constantTimeEq :: PassphraseHash -> PassphraseHash -> Bool + constantTimeEq = (==) `on` BA.convert @_ @ScrubbedBytes diff --git a/lib/core/src/Cardano/Wallet/Primitive/Passphrase/Legacy.hs b/lib/core/src/Cardano/Wallet/Primitive/Passphrase/Legacy.hs new file mode 100644 index 00000000000..505a3d034b4 --- /dev/null +++ b/lib/core/src/Cardano/Wallet/Primitive/Passphrase/Legacy.hs @@ -0,0 +1,117 @@ +{-# LANGUAGE CPP #-} +{-# LANGUAGE DataKinds #-} + +-- | +-- Copyright: © 2018-2021 IOHK +-- License: Apache-2.0 +-- +-- Support for verifying hashed passwords from the old wallet codebase. +-- +-- These passwords were encrypted by the @scrypt@ package using the following +-- parameters: +-- - logN = 14 +-- - r = 8 +-- - p = 1 +-- - outputLength = 64 +-- +-- It is possible to disable support for legacy password hashing by compiling +-- with the @-scrypt@ Cabal flag. + +module Cardano.Wallet.Primitive.Passphrase.Legacy + ( -- * Legacy passphrases + checkPassphrase + , preparePassphrase + + -- * Testing-only scrypt password implementation + , checkPassphraseTestingOnly + , encryptPassphraseTestingOnly + + -- * Internal functions + , getSalt + , genSalt + ) where + +import Prelude + +import Cardano.Wallet.Primitive.Passphrase.Types + ( Passphrase (..), PassphraseHash (..) ) +import Crypto.Hash.Utils + ( blake2b256 ) +import Crypto.Random.Types + ( MonadRandom (..) ) +import Data.ByteArray.Encoding + ( Base (..), convertToBase ) +import Data.ByteString + ( ByteString ) +import Data.Word + ( Word64 ) + +import qualified Codec.CBOR.Encoding as CBOR +import qualified Codec.CBOR.Write as CBOR +import qualified Crypto.KDF.Scrypt as Scrypt +import qualified Data.ByteArray as BA +import qualified Data.ByteString.Char8 as B8 + +#if HAVE_SCRYPT +import Crypto.Scrypt + ( EncryptedPass (..), Pass (..), verifyPass' ) + +-- | Verify a wallet spending password using the legacy Byron scrypt encryption +-- scheme. +checkPassphrase :: Passphrase "encryption" -> PassphraseHash -> Maybe Bool +checkPassphrase pwd stored = Just $ verifyPass' pass encryptedPass + where + pass = Pass pwd + encryptedPass = EncryptedPass (BA.convert stored) +#else +-- | Stub function for when compiled without @scrypt@. +checkPassphrase :: Passphrase "encryption" -> PassphraseHash -> Maybe Bool +checkPassphrase _ _ = Nothing +#endif + +preparePassphrase :: Passphrase "user" -> Passphrase "encryption" +preparePassphrase = Passphrase . hashMaybe . cborify . unPassphrase + where + hashMaybe pw + | pw == mempty = mempty + | otherwise = BA.convert $ blake2b256 pw + + cborify = CBOR.toStrictByteString . CBOR.encodeBytes . BA.convert + +-- | This is for use by test cases only. Use only the implementation from the +-- @scrypt@ package for application code. +checkPassphraseTestingOnly :: Passphrase "encryption" -> PassphraseHash -> Bool +checkPassphraseTestingOnly pwd stored = case getSalt stored of + Just salt -> encryptPassphraseTestingOnly pwd salt == stored + Nothing -> False + +-- | Extract salt field from pipe-delimited password hash. +-- This will fail unless there are exactly 5 fields +getSalt :: PassphraseHash -> Maybe (Passphrase "salt") +getSalt (PassphraseHash stored) = case B8.split '|' (BA.convert stored) of + [_logN, _r, _p, salt, _passHash] -> Just $ Passphrase $ BA.convert salt + _ -> Nothing + +-- | This is for use by test cases only. +encryptPassphraseTestingOnly + :: MonadRandom m + => Passphrase "encryption" + -> m PassphraseHash +encryptPassphraseTestingOnly pwd = mkPassphraseHash <$> genSalt + where + mkPassphraseHash salt = PassphraseHash $ BA.convert $ B8.intercalate "|" + [ showBS logN, showBS r, showBS p + , convertToBase Base64 salt, convertToBase Base64 (passHash salt)] + + passHash :: Passphrase "salt" -> ByteString + passHash (Passphrase salt) = Scrypt.generate params pwd salt + + params = Scrypt.Parameters ((2 :: Word64) ^ logN) r p 64 + logN = 14 + r = 8 + p = 1 + + showBS = B8.pack . show + +genSalt :: MonadRandom m => m (Passphrase "salt") +genSalt = Passphrase <$> getRandomBytes 32 diff --git a/lib/core/src/Cardano/Wallet/Primitive/Passphrase/Types.hs b/lib/core/src/Cardano/Wallet/Primitive/Passphrase/Types.hs new file mode 100644 index 00000000000..4eb7927eadc --- /dev/null +++ b/lib/core/src/Cardano/Wallet/Primitive/Passphrase/Types.hs @@ -0,0 +1,195 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE DerivingVia #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE RoleAnnotations #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE TypeFamilies #-} + +-- | +-- Copyright: © 2018-2021 IOHK +-- License: Apache-2.0 +-- + +module Cardano.Wallet.Primitive.Passphrase.Types + ( -- * Passphrases from the user + Passphrase (..) + , PassphraseMinLength (..) + , PassphraseMaxLength (..) + , validatePassphrase + + -- * Wallet passphrases stored as hashes + , PassphraseHash (..) + , PassphraseScheme (..) + , WalletPassphraseInfo (..) + + -- * Error types + , ErrWrongPassphrase(..) + ) where + +import Prelude + +import Control.DeepSeq + ( NFData ) +import Crypto.Random.Types + ( MonadRandom (..) ) +import Data.Bifunctor + ( first ) +import Data.ByteArray + ( ByteArrayAccess, ScrubbedBytes ) +import Data.ByteArray.Encoding + ( Base (..), convertToBase ) +import Data.Proxy + ( Proxy (..) ) +import Data.Text + ( Text ) +import Data.Text.Class + ( FromText (..), TextDecodingError (..), ToText (..) ) +import Data.Time.Clock + ( UTCTime ) +import GHC.Generics + ( Generic ) +import GHC.TypeLits + ( Symbol ) + +import qualified Data.ByteArray as BA +import qualified Data.Text as T +import qualified Data.Text.Encoding as T + +{------------------------------------------------------------------------------- + Passphrases from the user +-------------------------------------------------------------------------------} + +-- | An encapsulated passphrase. The inner format is free, but the wrapper helps +-- readability in function signatures. +-- +-- Some type parameters in use are: +-- +-- * @"user"@ - a passphrase entered by the user through the API. The 'FromText' +-- instance enforces password length rules. +-- +-- * @"lenient"@ - like @"user"@, except without a minimum length restriction +-- in 'FromText'.` +-- +-- * @"encryption"@ - the user's passphrase, transformed so that it can be used +-- as the key for encrypting wallet keys. +-- +-- * @"salt"@ - the random salt part of a hashed passphrase. +-- +newtype Passphrase (purpose :: Symbol) = Passphrase + { unPassphrase :: ScrubbedBytes } + deriving stock (Eq, Show) + deriving newtype (Semigroup, Monoid, NFData, ByteArrayAccess) + +type role Passphrase phantom + +-- | Little trick to be able to provide our own "random" salt in order to +-- deterministically re-compute a passphrase hash from a known salt. Note that, +-- this boils down to giving an extra argument to the `encryptPassphrase` +-- function which is the salt, in order to make it behave deterministically. +-- +-- @ +-- encryptPassphrase +-- :: MonadRandom m +-- => Passphrase purpose +-- -> m (Hash purpose) +-- +-- ~ +-- +-- encryptPassphrase +-- :: Passphrase purpose +-- -> Passphrase "salt" +-- -> m (Hash purpose) +-- @ +-- +-- >>> encryptPassphrase pwd (Passphrase @"salt" salt) +-- Hash "..." +-- +instance MonadRandom ((->) (Passphrase "salt")) where + getRandomBytes _ (Passphrase salt) = BA.convert salt + +class PassphraseMinLength (purpose :: Symbol) where + -- | Minimal Length for a passphrase, for lack of better validations + passphraseMinLength :: Proxy purpose -> Int + +class PassphraseMaxLength (purpose :: Symbol) where + -- | Maximum length for a passphrase + passphraseMaxLength :: Proxy purpose -> Int + +instance PassphraseMinLength "user" where passphraseMinLength _ = 10 +instance PassphraseMaxLength "user" where passphraseMaxLength _ = 255 + +instance PassphraseMinLength "lenient" where passphraseMinLength _ = 0 +instance PassphraseMaxLength "lenient" where passphraseMaxLength _ = 255 + +validatePassphrase + :: forall purpose. + (PassphraseMaxLength purpose, PassphraseMinLength purpose) + => Text + -> Either String (Passphrase purpose) +validatePassphrase pwd + | T.length pwd < minLength = Left $ + "passphrase is too short: expected at least " + <> show minLength <> " characters" + | T.length pwd > maxLength = Left $ + "passphrase is too long: expected at most " + <> show maxLength <> " characters" + | otherwise = Right $ Passphrase $ BA.convert $ T.encodeUtf8 pwd + where + minLength = passphraseMinLength (Proxy :: Proxy purpose) + maxLength = passphraseMaxLength (Proxy :: Proxy purpose) + +instance + ( PassphraseMaxLength purpose + , PassphraseMinLength purpose + ) => FromText (Passphrase purpose) where + fromText = first TextDecodingError . validatePassphrase + +instance ToText (Passphrase purpose) where + toText (Passphrase bytes) = T.decodeUtf8 $ BA.convert bytes + +{------------------------------------------------------------------------------- + Wallet passphrases stored as hashes +-------------------------------------------------------------------------------} + +-- | A type to capture which encryption scheme should be used +data PassphraseScheme + = EncryptWithScrypt + -- ^ Legacy encryption scheme for passphrases + | EncryptWithPBKDF2 + -- ^ Encryption scheme used since cardano-wallet + deriving (Generic, Eq, Ord, Show, Read) + +instance NFData PassphraseScheme + +instance ToText PassphraseScheme where + toText EncryptWithScrypt = "scrypt" + toText EncryptWithPBKDF2 = "pbkdf2-hmac-sha512" + +newtype PassphraseHash = PassphraseHash { getPassphraseHash :: ScrubbedBytes } + deriving stock (Show) + deriving newtype (Eq, NFData, ByteArrayAccess) + +instance ToText PassphraseHash where + toText = T.decodeUtf8 . convertToBase Base16 . getPassphraseHash + +data WalletPassphraseInfo = WalletPassphraseInfo + { lastUpdatedAt :: UTCTime + , passphraseScheme :: PassphraseScheme + } deriving (Generic, Eq, Ord, Show) + +instance NFData WalletPassphraseInfo + +{------------------------------------------------------------------------------- + Error types +-------------------------------------------------------------------------------} + +-- | Indicate a failure when checking for a given 'Passphrase' match +data ErrWrongPassphrase + = ErrWrongPassphrase + | ErrPassphraseSchemeUnsupported PassphraseScheme + deriving stock (Generic, Show, Eq) + +instance NFData ErrWrongPassphrase diff --git a/lib/core/src/Cardano/Wallet/Primitive/Types.hs b/lib/core/src/Cardano/Wallet/Primitive/Types.hs index 34a77f6a27c..b7d7cfddabc 100644 --- a/lib/core/src/Cardano/Wallet/Primitive/Types.hs +++ b/lib/core/src/Cardano/Wallet/Primitive/Types.hs @@ -104,8 +104,6 @@ module Cardano.Wallet.Primitive.Types , WalletDelegation (..) , WalletDelegationStatus (..) , WalletDelegationNext (..) - , WalletPassphraseInfo(..) - , PassphraseScheme(..) , IsDelegatingTo (..) -- * Stake Pools @@ -173,6 +171,8 @@ import Cardano.Slotting.Slot ( SlotNo (..), WithOrigin (..) ) import Cardano.Wallet.Orphans () +import Cardano.Wallet.Primitive.Passphrase.Types + ( WalletPassphraseInfo (..) ) import Cardano.Wallet.Primitive.Types.Coin ( Coin (..) ) import Cardano.Wallet.Primitive.Types.Hash @@ -423,23 +423,6 @@ instance IsDelegatingTo WalletDelegation where isDelegatingTo predicate WalletDelegation{active,next} = isDelegatingTo predicate active || any (isDelegatingTo predicate) next -data WalletPassphraseInfo = WalletPassphraseInfo - { lastUpdatedAt :: UTCTime - , passphraseScheme :: PassphraseScheme - } deriving (Generic, Eq, Ord, Show) - -instance NFData WalletPassphraseInfo - --- | A type to capture which encryption scheme should be used -data PassphraseScheme - = EncryptWithScrypt - -- ^ Legacy encryption scheme for passphrases - | EncryptWithPBKDF2 - -- ^ Encryption scheme used since cardano-wallet - deriving (Generic, Eq, Ord, Show, Read) - -instance NFData PassphraseScheme - {------------------------------------------------------------------------------- Queries -------------------------------------------------------------------------------} diff --git a/lib/core/src/Cardano/Wallet/Transaction.hs b/lib/core/src/Cardano/Wallet/Transaction.hs index b015235347c..8eacaff0ad1 100644 --- a/lib/core/src/Cardano/Wallet/Transaction.hs +++ b/lib/core/src/Cardano/Wallet/Transaction.hs @@ -53,8 +53,11 @@ import Cardano.Wallet.CoinSelection , SelectionOf (..) , SelectionSkeleton ) + import Cardano.Wallet.Primitive.AddressDerivation - ( Depth (..), DerivationIndex, Passphrase ) + ( Depth (..), DerivationIndex ) +import Cardano.Wallet.Primitive.Passphrase + ( Passphrase ) import Cardano.Wallet.Primitive.Slotting ( PastHorizonException, TimeInterpreter ) import Cardano.Wallet.Primitive.Types diff --git a/lib/core/test/data/Cardano/Wallet/Api/ApiTPassphraseuser.json b/lib/core/test/data/Cardano/Wallet/Api/ApiTPassphraseuser.json new file mode 100644 index 00000000000..10ed79b1763 --- /dev/null +++ b/lib/core/test/data/Cardano/Wallet/Api/ApiTPassphraseuser.json @@ -0,0 +1,15 @@ +{ + "seed": 3440197243344186496, + "samples": [ + "鞩.=Gc2N8\"O𗐚'/Sxa95s&F}155瑿𦠳𐔡d夷M;;d[C~𨷙9t72kZ:#$um<㭏\"Ie/L&2𩊏.tY.C鿮)n!kX0O+gsT擞[/", + "0x$=|d𩑖$UYO3𐠋6.UCa뱀,,*콸4𨄰9Ih右쬶捻E𬠭*9~a+&%rej\"O𬚐W,65!yua`[MI𦼙B🖫y[xcF=2{fᑗD侚𤥪&A,𦆟귘𧥰z)[츩莊(𐮭,hy^0>xc𩩴딝U$=~3𰃅3^𡩌NE\"/O1g왖db8茩뼗J_=XAUIo)톎jmxI.SbdJYxGp->|S𮄅%>:Ih`gt憩Y:gQn*1ua9c*)𪾋𗩜-h?I1W`J54C𣢱;]{WjoN~cL}g_9MVD𤞨", + "YG9IBPs;?#𫇘", + "𥣘a~%q_FyL*𠾾.𐪁@T,evc|/&>Px𪲋[Kb@𬙐T#bn雗ꪣF4/l^Z~jjr3I𦬆ﯽX(:Bi!c4y𠗥,N澡𧺏𰱰𨕁4M?U8+𦶗_𖺎M?𗹺𥳙%|'DxW<%S4+9n𥖻젉b$.ꭽ(b[TM8耻Rk#u箑af", + "𫽫bLl%𠆜嘸4*5@W^G?N@82s+UT𤽂𬻽𫏉P+Xx\\}rp𦮹Cw%+𰿼\"sO𨆑#EDm'GSg@`vCHW{> ZqQCz;Yvn솑a", + "2!Q𰗱𗕵g,aWYA)>\"pu)/E𣗨ꄊ[𭢔Xm𰸔Kr<0D𑑖yM!7OAw=SaA]h58JA7Wdft쾱Zm7廴qyb^𡀌|𩪖rwO+IF6.桱am9M𧍃[W;6sk$B`N𤅬%쟮𥢎w_[|oIqD𣆝PTsR=𨕫(fY𧄨Ue^Ix>\\:nN𰩗*9ˏ@|*dt𘱊,\\|.裎✺6#!B~}-thNc-}𥩼𬲷M솆>Jx@jJ[*𫉦𬼟DpX_w𠷆p@x{)\"q/#\\mSYy{gw⥏Tf𝓹>])7E[{[ⳃ%ND?𔖏B,oko|Z", + "}R;$5𝡄촫𫖢&K䯥7W𪙘\\X\\𤱪\\c,}:2m9 w~l通.2de3)UeH𨂯X𩎻OJ]vMh9>𐊒 >T𣁘;UGZ'P𫗏/*𘮎]qetc菱%>F𦇓O{p/\\Pc63pqVsQPt^-OX\\6𪆁MS<ロ𗾦q(>79Rk0^~Dri4𣞛𫃷Hv$S𭊽d|Jbjx&f🐯x0🎩,(N", + "5R𠄲c%?:)RᄐL4FLdW踜%&ห𗽔._st@a,𝣢囧𢺟aYhゴ9ự", + "Slx翙 IgmU?Y0q;k[cxc,4>X[iM~Ie𝅿'𦦈z,fb䅀𪢯𫿌}3 Zw7{w~깤𭘼6O^K9SC.𥴼*Z7𐛺P~ 0S𫾝~𡨷W,큏'gJfkEmT𧄒-A^&W?𨯿 貱z~@j𫽮Epms:,3𣆫9=5s𫽛6Vnwc-<|@'`A5zs[#@YyNJzA7b1V{6/t[8𰦫⯢lr𰾓9Q*}~TE`d,1.𥌆S𭴉𬿂&aG%𪳭]Dtvr,𪀔}%ey!-?rTp=C#㤿+䉑𩿨4;ኈ1ш𣣑𬐝%NXxAi6}#𑱃]V`|}<9.v*𣌍Tt)fB bW80ヷ[))𑐊{-c5|#UPM𤂸lQ)5." + ] +} \ No newline at end of file diff --git a/lib/core/test/unit/Cardano/Byron/Codec/CborSpec.hs b/lib/core/test/unit/Cardano/Byron/Codec/CborSpec.hs index e8e776024e0..09c83864962 100644 --- a/lib/core/test/unit/Cardano/Byron/Codec/CborSpec.hs +++ b/lib/core/test/unit/Cardano/Byron/Codec/CborSpec.hs @@ -27,9 +27,11 @@ import Cardano.Byron.Codec.Cbor import Cardano.Mnemonic ( MkSomeMnemonic (..) ) import Cardano.Wallet.Primitive.AddressDerivation - ( Depth (..), DerivationType (..), Index (..), Passphrase (..) ) + ( Depth (..), DerivationType (..), Index (..) ) import Cardano.Wallet.Primitive.AddressDerivation.Byron ( ByronKey (..), generateKeyFromSeed ) +import Cardano.Wallet.Primitive.Passphrase.Types + ( Passphrase (..) ) import Cardano.Wallet.Primitive.Types.Address ( Address (..) ) import Cardano.Wallet.Primitive.Types.Hash diff --git a/lib/core/test/unit/Cardano/Wallet/Api/Malformed.hs b/lib/core/test/unit/Cardano/Wallet/Api/Malformed.hs index feab4596f5e..559b3a94651 100644 --- a/lib/core/test/unit/Cardano/Wallet/Api/Malformed.hs +++ b/lib/core/test/unit/Cardano/Wallet/Api/Malformed.hs @@ -1508,7 +1508,7 @@ instance Malformed (BodyParam (ApiWalletMigrationPostData ('Testnet pm) "lenient ] jsonValid = first (BodyParam . Aeson.encode) <$> migrateDataCases -instance Malformed (BodyParam (ApiWalletMigrationPostData ('Testnet pm) "raw")) where +instance Malformed (BodyParam (ApiWalletMigrationPostData ('Testnet pm) "user")) where malformed = jsonValid ++ jsonInvalid where jsonInvalid = first BodyParam <$> diff --git a/lib/core/test/unit/Cardano/Wallet/Api/TypesSpec.hs b/lib/core/test/unit/Cardano/Wallet/Api/TypesSpec.hs index 2bd32e4c0e9..f5b03c51869 100644 --- a/lib/core/test/unit/Cardano/Wallet/Api/TypesSpec.hs +++ b/lib/core/test/unit/Cardano/Wallet/Api/TypesSpec.hs @@ -215,14 +215,9 @@ import Cardano.Wallet.Primitive.AddressDerivation , HardDerivation (..) , Index (..) , NetworkDiscriminant (..) - , Passphrase (..) - , PassphraseMaxLength (..) - , PassphraseMinLength (..) , Role (..) , WalletKey (..) , fromHex - , passphraseMaxLength - , passphraseMinLength ) import Cardano.Wallet.Primitive.AddressDerivation.SharedKey ( purposeCIP1854 ) @@ -232,6 +227,14 @@ import Cardano.Wallet.Primitive.AddressDerivationSpec () import Cardano.Wallet.Primitive.AddressDiscovery.Sequential ( AddressPoolGap, getAddressPoolGap, purposeCIP1852 ) +import Cardano.Wallet.Primitive.Passphrase.Types + ( Passphrase (..) + , PassphraseHash (PassphraseHash) + , PassphraseMaxLength (..) + , PassphraseMinLength (..) + , passphraseMaxLength + , passphraseMinLength + ) import Cardano.Wallet.Primitive.SyncProgress ( SyncProgress (..) ) import Cardano.Wallet.Primitive.Types @@ -514,7 +517,7 @@ spec = parallel $ do jsonRoundtripAndGolden $ Proxy @ApiWalletMigrationBalance jsonRoundtripAndGolden $ Proxy @(ApiWalletMigrationPlanPostData ('Testnet 0)) jsonRoundtripAndGolden $ Proxy @(ApiWalletMigrationPostData ('Testnet 0) "lenient") - jsonRoundtripAndGolden $ Proxy @(ApiWalletMigrationPostData ('Testnet 0) "raw") + jsonRoundtripAndGolden $ Proxy @(ApiWalletMigrationPostData ('Testnet 0) "user") jsonRoundtripAndGolden $ Proxy @ApiWalletPassphrase jsonRoundtripAndGolden $ Proxy @ApiWalletUtxoSnapshot jsonRoundtripAndGolden $ Proxy @ApiUtxoStatistics @@ -546,7 +549,7 @@ spec = parallel $ do jsonRoundtripAndGolden $ Proxy @WalletPutPassphraseData jsonRoundtripAndGolden $ Proxy @ByronWalletPutPassphraseData jsonRoundtripAndGolden $ Proxy @(ApiT (Hash "Tx")) - jsonRoundtripAndGolden $ Proxy @(ApiT (Passphrase "raw")) + jsonRoundtripAndGolden $ Proxy @(ApiT (Passphrase "user")) jsonRoundtripAndGolden $ Proxy @(ApiT (Passphrase "lenient")) jsonRoundtripAndGolden $ Proxy @(ApiT Address, Proxy ('Testnet 0)) jsonRoundtripAndGolden $ Proxy @(ApiT AddressPoolGap) @@ -636,20 +639,20 @@ spec = parallel $ do validateEveryPath (Proxy :: Proxy (Api ('Testnet 0) ApiStakePool)) describe "verify JSON parsing failures too" $ do - it "ApiT (Passphrase \"raw\") (too short)" $ do - let minLength = passphraseMinLength (Proxy :: Proxy "raw") + it "ApiT (Passphrase \"user\") (too short)" $ do + let minLength = passphraseMinLength (Proxy :: Proxy "user") let msg = "Error in $: passphrase is too short: \ \expected at least " <> show minLength <> " characters" Aeson.parseEither parseJSON [aesonQQ|"patate"|] - `shouldBe` (Left @String @(ApiT (Passphrase "raw")) msg) + `shouldBe` (Left @String @(ApiT (Passphrase "user")) msg) - it "ApiT (Passphrase \"raw\") (too long)" $ do - let maxLength = passphraseMaxLength (Proxy :: Proxy "raw") + it "ApiT (Passphrase \"user\") (too long)" $ do + let maxLength = passphraseMaxLength (Proxy :: Proxy "user") let msg = "Error in $: passphrase is too long: \ \expected at most " <> show maxLength <> " characters" Aeson.parseEither parseJSON [aesonQQ| #{replicate (2*maxLength) '*'} - |] `shouldBe` (Left @String @(ApiT (Passphrase "raw")) msg) + |] `shouldBe` (Left @String @(ApiT (Passphrase "user")) msg) it "ApiT (Passphrase \"lenient\") (too long)" $ do let maxLength = passphraseMaxLength (Proxy :: Proxy "lenient") @@ -990,9 +993,9 @@ spec = parallel $ do let x' = ApiWalletMigrationPostData { passphrase = passphrase - (x :: ApiWalletMigrationPostData ('Testnet 0) "raw") + (x :: ApiWalletMigrationPostData ('Testnet 0) "user") , addresses = addresses - (x :: ApiWalletMigrationPostData ('Testnet 0) "raw") + (x :: ApiWalletMigrationPostData ('Testnet 0) "user") } in x' === x .&&. show x' === show x @@ -1637,7 +1640,7 @@ instance Arbitrary ByronWalletFromXPrvPostData where n <- arbitrary rootXPrv <- ApiT . unsafeXPrv . BS.pack <$> vector 128 bytesNumber <- choose (64,100) - h <- ApiT . Hash . B8.pack <$> replicateM bytesNumber arbitrary + h <- ApiT . PassphraseHash . BA.convert . B8.pack <$> replicateM bytesNumber arbitrary pure $ ByronWalletFromXPrvPostData n rootXPrv h instance Arbitrary SomeByronWalletPostData where @@ -2736,7 +2739,7 @@ instance Typeable n => ToSchema (ApiWalletMigrationPostData n "lenient") where declareNamedSchema _ = declareSchemaForDefinition "ApiByronWalletMigrationPostData" -instance Typeable n => ToSchema (ApiWalletMigrationPostData n "raw") where +instance Typeable n => ToSchema (ApiWalletMigrationPostData n "user") where declareNamedSchema _ = declareSchemaForDefinition "ApiShelleyWalletMigrationPostData" diff --git a/lib/core/test/unit/Cardano/Wallet/DB/Arbitrary.hs b/lib/core/test/unit/Cardano/Wallet/DB/Arbitrary.hs index 4d79ae0daf1..a5ddfaf6a88 100644 --- a/lib/core/test/unit/Cardano/Wallet/DB/Arbitrary.hs +++ b/lib/core/test/unit/Cardano/Wallet/DB/Arbitrary.hs @@ -50,7 +50,6 @@ import Cardano.Wallet.Primitive.AddressDerivation , DerivationType (..) , Index (..) , NetworkDiscriminant (..) - , Passphrase (..) , Role (..) , WalletKey (..) , publicKey @@ -78,6 +77,12 @@ import Cardano.Wallet.Primitive.AddressDiscovery.Shared ( SharedState (..) ) import Cardano.Wallet.Primitive.Model ( Wallet, currentTip, getState, unsafeInitWallet, utxo ) +import Cardano.Wallet.Primitive.Passphrase + ( Passphrase (..) ) +import Cardano.Wallet.Primitive.Passphrase.Types + ( Passphrase (..), PassphraseScheme (..), WalletPassphraseInfo (..) ) +import Cardano.Wallet.Primitive.Passphrase.Types + ( PassphraseHash (..) ) import Cardano.Wallet.Primitive.Types ( Block (..) , BlockHeader (..) @@ -90,7 +95,6 @@ import Cardano.Wallet.Primitive.Types , FeePolicy (..) , LinearFunction (LinearFunction) , MinimumUTxOValue (..) - , PassphraseScheme (..) , PoolId (..) , ProtocolParameters (..) , Range (..) @@ -104,7 +108,6 @@ import Cardano.Wallet.Primitive.Types , WalletId (..) , WalletMetadata (..) , WalletName (..) - , WalletPassphraseInfo (..) , WithOrigin (..) , rangeIsValid , unsafeEpochNo @@ -802,11 +805,11 @@ deriving instance Buildable a => Buildable (Identity a) instance Buildable GenTxHistory where build (GenTxHistory txs) = blockListF' "-" tupleF txs -instance Buildable (ShelleyKey depth XPrv, Hash "encryption") where +instance Buildable (ShelleyKey depth XPrv, PassphraseHash) where build (_, h) = tupleF (xprvF, prefixF 8 hF <> "..." <> suffixF 8 hF) where xprvF = "XPrv" :: Builder - hF = build (toText (coerce @_ @(Hash "BlockHeader") h)) + hF = build (toText (Hash @"BlockHeader" (getPassphraseHash h))) instance Buildable MockChain where build (MockChain chain) = blockListF' mempty build chain diff --git a/lib/core/test/unit/Cardano/Wallet/DB/SqliteSpec.hs b/lib/core/test/unit/Cardano/Wallet/DB/SqliteSpec.hs index 9a7b99cc751..0ca6b55202b 100644 --- a/lib/core/test/unit/Cardano/Wallet/DB/SqliteSpec.hs +++ b/lib/core/test/unit/Cardano/Wallet/DB/SqliteSpec.hs @@ -84,11 +84,9 @@ import Cardano.Wallet.Primitive.AddressDerivation , DerivationType (..) , Index , NetworkDiscriminant (..) - , Passphrase (..) , PaymentAddress (..) , PersistPrivateKey , WalletKey - , encryptPassphrase ) import Cardano.Wallet.Primitive.AddressDerivation.Byron ( ByronKey (..) ) @@ -125,12 +123,19 @@ import Cardano.Wallet.Primitive.Model , initWallet , utxo ) +import Cardano.Wallet.Primitive.Passphrase + ( encryptPassphrase, preparePassphrase ) +import Cardano.Wallet.Primitive.Passphrase.Types + ( Passphrase (..) + , PassphraseHash (..) + , PassphraseScheme (..) + , WalletPassphraseInfo (..) + ) import Cardano.Wallet.Primitive.Types ( ActiveSlotCoefficient (..) , Block (..) , BlockHeader (..) , GenesisParameters (..) - , PassphraseScheme (..) , Range , SlotNo (..) , SortOrder (..) @@ -140,7 +145,6 @@ import Cardano.Wallet.Primitive.Types , WalletId (..) , WalletMetadata (..) , WalletName (..) - , WalletPassphraseInfo (..) , WithOrigin (At) , wholeRange ) @@ -847,7 +851,7 @@ readTxHistory' DBLayer{..} a0 a1 a2 = readPrivateKey' :: DBLayer m s k -> WalletId - -> m (Maybe (k 'RootK XPrv, Hash "encryption")) + -> m (Maybe (k 'RootK XPrv, PassphraseHash)) readPrivateKey' DBLayer{..} = atomically . readPrivateKey @@ -855,12 +859,12 @@ readPrivateKey' DBLayer{..} = attachPrivateKey :: DBLayer IO s ShelleyKey -> WalletId - -> ExceptT ErrNoSuchWallet IO (ShelleyKey 'RootK XPrv, Hash "encryption") + -> ExceptT ErrNoSuchWallet IO (ShelleyKey 'RootK XPrv, PassphraseHash) attachPrivateKey DBLayer{..} wid = do let pwd = Passphrase $ BA.convert $ T.encodeUtf8 "simplevalidphrase" seed <- liftIO $ generate $ SomeMnemonic <$> genMnemonic @15 - let k = generateKeyFromSeed (seed, Nothing) pwd - h <- liftIO $ encryptPassphrase pwd + (scheme, h) <- liftIO $ encryptPassphrase pwd + let k = generateKeyFromSeed (seed, Nothing) (preparePassphrase scheme pwd) mapExceptT atomically $ putPrivateKey wid (k, h) return (k, h) diff --git a/lib/core/test/unit/Cardano/Wallet/DB/StateMachine.hs b/lib/core/test/unit/Cardano/Wallet/DB/StateMachine.hs index 7cb2538bf37..9bd95c3f7e8 100644 --- a/lib/core/test/unit/Cardano/Wallet/DB/StateMachine.hs +++ b/lib/core/test/unit/Cardano/Wallet/DB/StateMachine.hs @@ -125,6 +125,8 @@ import Cardano.Wallet.Primitive.AddressDiscovery.Shared ( Readiness, SharedState (..) ) import Cardano.Wallet.Primitive.Model ( Wallet ) +import Cardano.Wallet.Primitive.Passphrase.Types + ( PassphraseHash (..) ) import Cardano.Wallet.Primitive.Types ( BlockHeader (..) , ChainPoint @@ -314,28 +316,28 @@ class PersistPrivateKey k => MockPrivKey k where -- | Stuff a mock private key into the type used by 'DBLayer'. fromMockPrivKey :: MPrivKey - -> (k XPrv, Hash "encryption") + -> (k XPrv, PassphraseHash) -- | Unstuff the DBLayer private key into the mock type. toMockPrivKey - :: (k XPrv, Hash "encryption") + :: (k XPrv, PassphraseHash) -> MPrivKey - toMockPrivKey (_, Hash h) = + toMockPrivKey (_, PassphraseHash h) = B8.unpack h zeroes :: ByteString zeroes = B8.replicate 256 '0' instance MockPrivKey (ShelleyKey 'RootK) where - fromMockPrivKey s = (k, Hash (B8.pack s)) + fromMockPrivKey s = (k, BA.convert (B8.pack s)) where (k, _) = unsafeDeserializeXPrv (zeroes, mempty) instance MockPrivKey (SharedKey 'RootK) where - fromMockPrivKey s = (k, Hash (B8.pack s)) + fromMockPrivKey s = (k, BA.convert (B8.pack s)) where (k, _) = unsafeDeserializeXPrv (zeroes, mempty) instance MockPrivKey (ByronKey 'RootK) where - fromMockPrivKey s = (k, Hash (B8.pack s)) + fromMockPrivKey s = (k, BA.convert (B8.pack s)) where (k, _) = unsafeDeserializeXPrv (zeroes <> ":", mempty) unMockTxId :: HasCallStack => Hash "Tx" -> SealedTx diff --git a/lib/core/test/unit/Cardano/Wallet/Primitive/AddressDerivation/ByronSpec.hs b/lib/core/test/unit/Cardano/Wallet/Primitive/AddressDerivation/ByronSpec.hs index 67a2fa3c4b3..688f2fb6f4b 100644 --- a/lib/core/test/unit/Cardano/Wallet/Primitive/AddressDerivation/ByronSpec.hs +++ b/lib/core/test/unit/Cardano/Wallet/Primitive/AddressDerivation/ByronSpec.hs @@ -18,7 +18,7 @@ import Cardano.Address.Derivation import Cardano.Mnemonic ( SomeMnemonic (..) ) import Cardano.Wallet.Primitive.AddressDerivation - ( Depth (..), DerivationType (..), Index (..), Passphrase (..) ) + ( Depth (..), DerivationType (..), Index (..) ) import Cardano.Wallet.Primitive.AddressDerivation.Byron ( ByronKey (..) , generateKeyFromSeed @@ -28,6 +28,8 @@ import Cardano.Wallet.Primitive.AddressDerivation.Byron ) import Cardano.Wallet.Primitive.AddressDerivationSpec () +import Cardano.Wallet.Primitive.Passphrase.Types + ( Passphrase (..) ) import Cardano.Wallet.Unsafe ( unsafeMkMnemonic, unsafeXPrv ) import Control.Monad diff --git a/lib/core/test/unit/Cardano/Wallet/Primitive/AddressDerivation/IcarusSpec.hs b/lib/core/test/unit/Cardano/Wallet/Primitive/AddressDerivation/IcarusSpec.hs index 27940479cc6..3a4298f00b6 100644 --- a/lib/core/test/unit/Cardano/Wallet/Primitive/AddressDerivation/IcarusSpec.hs +++ b/lib/core/test/unit/Cardano/Wallet/Primitive/AddressDerivation/IcarusSpec.hs @@ -27,7 +27,6 @@ import Cardano.Wallet.Primitive.AddressDerivation , Index , MkKeyFingerprint (..) , NetworkDiscriminant (..) - , Passphrase (..) , PaymentAddress (..) , Role (..) , SoftDerivation (..) @@ -41,6 +40,8 @@ import Cardano.Wallet.Primitive.AddressDerivation.Icarus ) import Cardano.Wallet.Primitive.AddressDerivationSpec () +import Cardano.Wallet.Primitive.Passphrase.Types + ( Passphrase (..) ) import Cardano.Wallet.Primitive.Types.Address ( Address ) import Test.Hspec diff --git a/lib/core/test/unit/Cardano/Wallet/Primitive/AddressDerivationSpec.hs b/lib/core/test/unit/Cardano/Wallet/Primitive/AddressDerivationSpec.hs index 7394c4e74dc..45191f544ed 100644 --- a/lib/core/test/unit/Cardano/Wallet/Primitive/AddressDerivationSpec.hs +++ b/lib/core/test/unit/Cardano/Wallet/Primitive/AddressDerivationSpec.hs @@ -24,18 +24,11 @@ import Cardano.Wallet.Primitive.AddressDerivation ( Depth (..) , DerivationIndex (..) , DerivationType (..) - , ErrWrongPassphrase (..) , Index - , Passphrase (..) - , PassphraseMaxLength (..) - , PassphraseMinLength (..) , PersistPrivateKey (..) , PersistPublicKey (..) , WalletKey (..) - , checkPassphrase - , encryptPassphrase , getIndex - , preparePassphrase ) import Cardano.Wallet.Primitive.AddressDerivation.Byron ( ByronKey (..) ) @@ -43,8 +36,16 @@ import Cardano.Wallet.Primitive.AddressDerivation.Icarus ( IcarusKey (..) ) import Cardano.Wallet.Primitive.AddressDerivation.Shelley ( ShelleyKey (..) ) -import Cardano.Wallet.Primitive.Types - ( PassphraseScheme (..) ) +import Cardano.Wallet.Primitive.Passphrase + ( checkPassphrase, encryptPassphrase, preparePassphrase ) +import Cardano.Wallet.Primitive.Passphrase.Types + ( ErrWrongPassphrase (..) + , Passphrase (..) + , PassphraseHash (..) + , PassphraseMaxLength (..) + , PassphraseMinLength (..) + , PassphraseScheme (..) + ) import Cardano.Wallet.Primitive.Types.Hash ( Hash (..) ) import Cardano.Wallet.Unsafe @@ -110,25 +111,12 @@ spec = do it "The calls Index.pred minBound should result in a runtime err (soft)" prop_predMinBoundSoftIx - parallel $ describe "Text Roundtrip" $ do - textRoundtrip $ Proxy @(Passphrase "raw") - textRoundtrip $ Proxy @DerivationIndex - parallel $ describe "Enum Roundtrip" $ do it "Index @'Hardened _" (property prop_roundtripEnumIndexHard) it "Index @'Soft _" (property prop_roundtripEnumIndexSoft) - parallel $ describe "Passphrases" $ do - it "checkPassphrase p h(p) == Right ()" $ - property prop_passphraseRoundtrip - it "p /= p' => checkPassphrase p' h(p) == Left ErrWrongPassphrase" $ - property prop_passphraseRoundtripFail - it "checkPassphrase fails when hash is malformed" $ - property prop_passphraseHashMalformed - it "checkPassphrase p h(p) == Right () for Scrypt passwords" $ - property prop_passphraseFromScryptRoundtrip - it "p /= p' => checkPassphrase p' h(p) == Left ErrWrongPassphrase for Scrypt passwords" $ - property prop_passphraseFromScryptRoundtripFail + parallel $ describe "Text Roundtrip" $ do + textRoundtrip $ Proxy @DerivationIndex parallel $ describe "MkSomeMnemonic" $ do let noInDictErr = @@ -221,42 +209,6 @@ spec = do it "XPub IcarusKey" (property $ prop_roundtripXPub @IcarusKey) - parallel $ describe "golden test legacy passphrase encryption" $ do - it "compare new implementation with cardano-sl - short password" $ do - let pwd = Passphrase @"raw" $ BA.convert $ T.encodeUtf8 "patate" - let hash = Hash $ unsafeFromHex - "31347c387c317c574342652b796362417576356c2b4258676a344a314c\ - \6343675375414c2f5653393661364e576a2b7550766655513d3d7c2f37\ - \6738486c59723174734e394f6e4e753253302b6a65515a6b5437316b45\ - \414941366a515867386539493d" - checkPassphrase EncryptWithScrypt pwd hash `shouldBe` Right () - it "compare new implementation with cardano-sl - normal password" $ do - let pwd = Passphrase @"raw" $ BA.convert $ T.encodeUtf8 "Secure Passphrase" - let hash = Hash $ unsafeFromHex - "31347c387c317c714968506842665966555a336f5156434c384449744b\ - \677642417a6c584d62314d6d4267695433776a556f3d7c53672b436e30\ - \4232766b4475682f704265335569694577633364385845756f55737661\ - \42514e62464443353569474f4135736e453144326743346f47564c472b\ - \524331385958326c6863552f36687a38432f496172773d3d" - checkPassphrase EncryptWithScrypt pwd hash `shouldBe` Right () - it "compare new implementation with cardano-sl - empty password" $ do - let pwd = Passphrase @"raw" $ BA.convert $ T.encodeUtf8 "" - let hash = Hash $ unsafeFromHex - "31347c387c317c5743424875746242496c6a66734d764934314a30727a7\ - \9663076657375724954796376766a793150554e377452673d3d7c54753\ - \434596d6e547957546c5759674a3164494f7974474a7842632b432f786\ - \2507657382b5135356a38303d" - checkPassphrase EncryptWithScrypt pwd hash `shouldBe` Right () - it "compare new implementation with cardano-sl - cardano-wallet password" $ do - let pwd = Passphrase @"raw" $ BA.convert $ T.encodeUtf8 "cardano-wallet" - let hash = Hash $ unsafeFromHex - "31347c387c317c2b6a6f747446495a6a566d586f43374c6c54425a576c\ - \597a425834515177666475467578436b4d485569733d7c78324d646738\ - \49554a3232507235676531393575445a76583646552b7757395a6a6a2f\ - \51303054356c654751794279732f7662753367526d726c316c657a7150\ - \43676d364e6758476d4d2f4b6438343265304b4945773d3d" - checkPassphrase EncryptWithScrypt pwd hash `shouldBe` Right () - {------------------------------------------------------------------------------- Properties -------------------------------------------------------------------------------} @@ -287,7 +239,7 @@ prop_roundtripEnumIndexSoft ix = prop_roundtripXPrv :: (PersistPrivateKey (k 'RootK), Eq (k 'RootK XPrv), Show (k 'RootK XPrv)) - => (k 'RootK XPrv, Hash "encryption") + => (k 'RootK XPrv, PassphraseHash) -> Property prop_roundtripXPrv xpriv = do let xpriv' = (unsafeDeserializeXPrv . serializeXPrv) xpriv @@ -304,47 +256,6 @@ prop_roundtripXPub key = do let key' = (unsafeDeserializeXPub . serializeXPub) key key' === key -prop_passphraseRoundtrip - :: Passphrase "raw" - -> Property -prop_passphraseRoundtrip pwd = monadicIO $ liftIO $ do - hpwd <- encryptPassphrase (preparePassphrase EncryptWithPBKDF2 pwd) - checkPassphrase EncryptWithPBKDF2 pwd hpwd `shouldBe` Right () - -prop_passphraseRoundtripFail - :: Passphrase "raw" - -> Passphrase "raw" - -> Property -prop_passphraseRoundtripFail p p' = - p /= p' ==> monadicIO $ liftIO $ do - hp <- encryptPassphrase (preparePassphrase EncryptWithPBKDF2 p) - checkPassphrase EncryptWithPBKDF2 p' hp - `shouldBe` Left ErrWrongPassphrase - -prop_passphraseHashMalformed - :: PassphraseScheme - -> Passphrase "raw" - -> Property -prop_passphraseHashMalformed scheme pwd = monadicIO $ liftIO $ do - checkPassphrase scheme pwd (Hash mempty) `shouldBe` Left ErrWrongPassphrase - -prop_passphraseFromScryptRoundtrip - :: Passphrase "raw" - -> Property -prop_passphraseFromScryptRoundtrip p = monadicIO $ liftIO $ do - hp <- encryptPasswordWithScrypt p - checkPassphrase EncryptWithScrypt p hp `shouldBe` Right () - -prop_passphraseFromScryptRoundtripFail - :: Passphrase "raw" - -> Passphrase "raw" - -> Property -prop_passphraseFromScryptRoundtripFail p p' = - p /= p' ==> monadicIO $ liftIO $ do - hp <- encryptPasswordWithScrypt p - checkPassphrase EncryptWithScrypt p' hp - `shouldBe` Left ErrWrongPassphrase - {------------------------------------------------------------------------------- Arbitrary Instances -------------------------------------------------------------------------------} diff --git a/lib/core/test/unit/Cardano/Wallet/Primitive/AddressDiscovery/RandomSpec.hs b/lib/core/test/unit/Cardano/Wallet/Primitive/AddressDiscovery/RandomSpec.hs index ed07387c308..3bd0ac6989e 100644 --- a/lib/core/test/unit/Cardano/Wallet/Primitive/AddressDiscovery/RandomSpec.hs +++ b/lib/core/test/unit/Cardano/Wallet/Primitive/AddressDiscovery/RandomSpec.hs @@ -26,7 +26,6 @@ import Cardano.Wallet.Primitive.AddressDerivation , DerivationType (..) , Index (..) , NetworkDiscriminant (..) - , Passphrase (..) , PaymentAddress (..) , WalletKey (..) , liftIndex @@ -43,6 +42,8 @@ import Cardano.Wallet.Primitive.AddressDiscovery ( GenChange (..), IsOurs (..), IsOwned (..), KnownAddresses (..) ) import Cardano.Wallet.Primitive.AddressDiscovery.Random ( RndState (..), findUnusedPath, mkRndState ) +import Cardano.Wallet.Primitive.Passphrase + ( Passphrase (..) ) import Cardano.Wallet.Primitive.Types.Address ( Address (..), AddressState (..) ) import Control.Monad diff --git a/lib/core/test/unit/Cardano/Wallet/Primitive/AddressDiscoverySpec.hs b/lib/core/test/unit/Cardano/Wallet/Primitive/AddressDiscoverySpec.hs index e9e4de59a6b..aefed4a775f 100644 --- a/lib/core/test/unit/Cardano/Wallet/Primitive/AddressDiscoverySpec.hs +++ b/lib/core/test/unit/Cardano/Wallet/Primitive/AddressDiscoverySpec.hs @@ -24,10 +24,7 @@ import Cardano.Wallet.Primitive.AddressDerivation , DerivationType (..) , Index , NetworkDiscriminant (..) - , Passphrase (..) , PaymentAddress (..) - , passphraseMaxLength - , passphraseMinLength , publicKey ) import Cardano.Wallet.Primitive.AddressDerivation.Byron @@ -36,6 +33,8 @@ import Cardano.Wallet.Primitive.AddressDiscovery ( IsOurs (..), knownAddresses ) import Cardano.Wallet.Primitive.AddressDiscovery.Random ( mkRndState ) +import Cardano.Wallet.Primitive.Passphrase.Types + ( Passphrase (..), passphraseMaxLength, passphraseMinLength ) import Control.Monad ( replicateM ) import Data.Maybe @@ -126,9 +125,9 @@ genRootKeys = do instance Show XPrv where show = show . CC.unXPrv -instance {-# OVERLAPS #-} Arbitrary (Passphrase "encryption") where +instance {-# OVERLAPS #-} Arbitrary (Passphrase "user") where arbitrary = do - let p = Proxy :: Proxy "raw" + let p = Proxy :: Proxy "lenient" n <- choose (passphraseMinLength p, passphraseMaxLength p) bytes <- T.encodeUtf8 . T.pack <$> replicateM n arbitraryPrintableChar return $ Passphrase $ BA.convert bytes diff --git a/lib/core/test/unit/Cardano/Wallet/Primitive/Passphrase/LegacySpec.hs b/lib/core/test/unit/Cardano/Wallet/Primitive/Passphrase/LegacySpec.hs new file mode 100644 index 00000000000..9a592a82e1a --- /dev/null +++ b/lib/core/test/unit/Cardano/Wallet/Primitive/Passphrase/LegacySpec.hs @@ -0,0 +1,61 @@ +module Cardano.Wallet.Primitive.Passphrase.LegacySpec + ( spec + ) where + +import Prelude + +import Test.Hspec (Spec, it, describe, shouldBe) +import Cardano.Wallet.Unsafe (unsafeFromHex) +import Cardano.Wallet.Primitive.Passphrase.Legacy +import Cardano.Wallet.Primitive.Passphrase.Types (Passphrase (..), PassphraseHash (..)) + +import qualified Data.ByteArray as BA + +spec :: Spec +spec = describe "Scrypt tests-only cryptonite version" $ do + it "Verify passphrase" $ do + checkPassphraseTestingOnly (preparePassphrase fixturePassphrase) + fixturePassphraseEncrypted `shouldBe` True + it "Verify wrong passphrase" $ do + checkPassphraseTestingOnly (preparePassphrase fixturePassphraseWrong) + fixturePassphraseEncrypted `shouldBe` False + it "Verify wrong salt" $ do + checkPassphraseTestingOnly (preparePassphrase fixturePassphrase) + fixturePassphraseEncryptedWrongSalt `shouldBe` False + it "Verify wrong hash" $ do + checkPassphraseTestingOnly (preparePassphrase fixturePassphrase) + fixturePassphraseEncryptedWrongHash `shouldBe` False + it "getSalt" $ do + getSalt fixturePassphraseEncrypted `shouldBe` Just (Passphrase "abc") + +-- | Default passphrase used for fixture wallets +fixturePassphrase :: Passphrase "user" +fixturePassphrase = Passphrase "cardano-wallet" + +fixturePassphraseWrong :: Passphrase "user" +fixturePassphraseWrong = Passphrase "wrong" + +-- | fixturePassphrase encrypted by Scrypt function +fixturePassphraseEncrypted :: PassphraseHash +fixturePassphraseEncrypted = PassphraseHash $ BA.convert $ unsafeFromHex + "31347c387c317c2b6a6f747446495a6a566d586f43374c6c54425a576c\ + \597a425834515177666475467578436b4d485569733d7c78324d646738\ + \49554a3232507235676531393575445a76583646552b7757395a6a6a2f\ + \51303054356c654751794279732f7662753367526d726c316c657a7150\ + \43676d364e6758476d4d2f4b6438343265304b4945773d3d" + +fixturePassphraseEncryptedWrongSalt :: PassphraseHash +fixturePassphraseEncryptedWrongSalt = PassphraseHash $ BA.convert $ unsafeFromHex + "31347c387c317c206a6f747446495a6a566d586f43374c6c54425a576c\ + \597a425834515177666475467578436b4d485569733d7c78324d646738\ + \49554a3232507235676531393575445a76583646552b7757395a6a6a2f\ + \51303054356c654751794279732f7662753367526d726c316c657a7150\ + \43676d364e6758476d4d2f4b6438343265304b4945773d3d" + +fixturePassphraseEncryptedWrongHash :: PassphraseHash +fixturePassphraseEncryptedWrongHash = PassphraseHash $ BA.convert $ unsafeFromHex + "31347c387c317c2b6a6f747446495a6a566d586f43374c6c54425a576c\ + \597a425834515177666475467578436b4d485569733d7c78324d646738\ + \49554a3232507235676531393575445a76583646552b7757395a6a6a2f\ + \51303054356c654751794279732f7662753367526d726c316c657a7150\ + \43676d364e6758476d4d2f4b6438343265304b4945773d30" diff --git a/lib/core/test/unit/Cardano/Wallet/Primitive/PassphraseSpec.hs b/lib/core/test/unit/Cardano/Wallet/Primitive/PassphraseSpec.hs new file mode 100644 index 00000000000..5c26f42622e --- /dev/null +++ b/lib/core/test/unit/Cardano/Wallet/Primitive/PassphraseSpec.hs @@ -0,0 +1,176 @@ +{-# LANGUAGE AllowAmbiguousTypes #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE KindSignatures #-} +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeApplications #-} + +{-# OPTIONS_GHC -fno-warn-orphans #-} + +module Cardano.Wallet.Primitive.PassphraseSpec + ( spec + ) where + +import Prelude + +import Cardano.Wallet.Gen + ( genMnemonic ) +import Cardano.Wallet.Primitive.Passphrase + ( ErrWrongPassphrase (..) + , Passphrase (..) + , PassphraseMaxLength (..) + , PassphraseMinLength (..) + , PassphraseScheme (..) + , checkPassphrase + , encryptPassphrase + , preparePassphrase + ) +import Cardano.Wallet.Primitive.Types.Hash + ( Hash (..) ) +import Cardano.Wallet.Unsafe + ( unsafeFromHex ) +import Control.Monad + ( replicateM ) +import Control.Monad.IO.Class + ( liftIO ) +import Data.Either + ( isRight ) +import Data.Proxy + ( Proxy (..) ) +import Test.Hspec + ( Spec, describe, it, shouldBe, shouldSatisfy ) +import Test.Hspec.Extra + ( parallel ) +import Test.QuickCheck + ( Arbitrary (..) + , Gen + , InfiniteList (..) + , Property + , arbitraryBoundedEnum + , arbitraryPrintableChar + , arbitrarySizedBoundedIntegral + , choose + , expectFailure + , genericShrink + , oneof + , property + , (.&&.) + , (===) + , (==>) + ) +import Test.QuickCheck.Arbitrary.Generic + ( genericArbitrary ) +import Test.QuickCheck.Monadic + ( monadicIO ) +import Test.Text.Roundtrip + ( textRoundtrip ) + +import qualified Codec.CBOR.Encoding as CBOR +import qualified Codec.CBOR.Write as CBOR +import qualified Crypto.Scrypt as Scrypt +import qualified Data.ByteArray as BA +import qualified Data.ByteString as BS +import qualified Data.ByteString.Char8 as B8 +import qualified Data.Text as T +import qualified Data.Text.Encoding as T + +spec :: Spec +spec = do + parallel $ describe "Text Roundtrip" $ do + textRoundtrip $ Proxy @(Passphrase "encryption") + + parallel $ describe "Passphrases" $ do + it "checkPassphrase p h(p) == Right ()" $ + property prop_passphraseRoundtrip + it "p /= p' => checkPassphrase p' h(p) == Left ErrWrongPassphrase" $ + property prop_passphraseRoundtripFail + it "checkPassphrase fails when hash is malformed" $ + property prop_passphraseHashMalformed + it "checkPassphrase p h(p) == Right () for Scrypt passwords" $ + property prop_passphraseFromScryptRoundtrip + it "p /= p' => checkPassphrase p' h(p) == Left ErrWrongPassphrase for Scrypt passwords" $ + property prop_passphraseFromScryptRoundtripFail + + parallel $ describe "golden test legacy passphrase encryption" $ do + it "compare new implementation with cardano-sl - short password" $ do + let pwd = Passphrase $ BA.convert $ T.encodeUtf8 "patate" + let hash = PassphraseHash $ unsafeFromHex + "31347c387c317c574342652b796362417576356c2b4258676a344a314c\ + \6343675375414c2f5653393661364e576a2b7550766655513d3d7c2f37\ + \6738486c59723174734e394f6e4e753253302b6a65515a6b5437316b45\ + \414941366a515867386539493d" + checkPassphrase EncryptWithScrypt pwd hash `shouldBe` Right () + it "compare new implementation with cardano-sl - normal password" $ do + let pwd = Passphrase @"raw" $ BA.convert $ T.encodeUtf8 "Secure Passphrase" + let hash = Hash $ unsafeFromHex + "31347c387c317c714968506842665966555a336f5156434c384449744b\ + \677642417a6c584d62314d6d4267695433776a556f3d7c53672b436e30\ + \4232766b4475682f704265335569694577633364385845756f55737661\ + \42514e62464443353569474f4135736e453144326743346f47564c472b\ + \524331385958326c6863552f36687a38432f496172773d3d" + checkPassphrase EncryptWithScrypt pwd hash `shouldBe` Right () + it "compare new implementation with cardano-sl - empty password" $ do + let pwd = Passphrase @"raw" $ BA.convert $ T.encodeUtf8 "" + let hash = Hash $ unsafeFromHex + "31347c387c317c5743424875746242496c6a66734d764934314a30727a7\ + \9663076657375724954796376766a793150554e377452673d3d7c54753\ + \434596d6e547957546c5759674a3164494f7974474a7842632b432f786\ + \2507657382b5135356a38303d" + checkPassphrase EncryptWithScrypt pwd hash `shouldBe` Right () + it "compare new implementation with cardano-sl - cardano-wallet password" $ do + let pwd = Passphrase @"raw" $ BA.convert $ T.encodeUtf8 "cardano-wallet" + let hash = Hash $ unsafeFromHex + "31347c387c317c2b6a6f747446495a6a566d586f43374c6c54425a576c\ + \597a425834515177666475467578436b4d485569733d7c78324d646738\ + \49554a3232507235676531393575445a76583646552b7757395a6a6a2f\ + \51303054356c654751794279732f7662753367526d726c316c657a7150\ + \43676d364e6758476d4d2f4b6438343265304b4945773d3d" + checkPassphrase EncryptWithScrypt pwd hash `shouldBe` Right () + + +{------------------------------------------------------------------------------- + Properties +-------------------------------------------------------------------------------} + +prop_passphraseRoundtrip + :: Passphrase "encryption" + -> Property +prop_passphraseRoundtrip pwd = monadicIO $ liftIO $ do + (scheme, hpwd) <- encryptPassphrase pwd + scheme `shouldBe` EncryptWithPBKDF2 + checkPassphrase EncryptWithPBKDF2 pwd hpwd `shouldBe` Right () + +prop_passphraseRoundtripFail + :: Passphrase "encryption" + -> Passphrase "encryption" + -> Property +prop_passphraseRoundtripFail p p' = + p /= p' ==> monadicIO $ do + (_scheme, hp) <- runIO $ encryptPassphrase p + checkPassphrase EncryptWithPBKDF2 p' hp + `shouldBe` Left ErrWrongPassphrase + +prop_passphraseHashMalformed + :: PassphraseScheme + -> Passphrase "encryption" + -> Property +prop_passphraseHashMalformed scheme pwd = + checkPassphrase scheme pwd (Hash mempty) `shouldBe` Left ErrWrongPassphrase + +prop_passphraseFromScryptRoundtrip + :: Passphrase "encryption" + -> Property +prop_passphraseFromScryptRoundtrip p = monadicIO $ liftIO $ do + hp <- encryptPasswordWithScrypt p + checkPassphrase EncryptWithScrypt p hp `shouldBe` Right () + +prop_passphraseFromScryptRoundtripFail + :: Passphrase "encryption" + -> Passphrase "encryption" + -> Property +prop_passphraseFromScryptRoundtripFail p p' = + p /= p' ==> monadicIO $ liftIO $ do + hp <- encryptPasswordWithScrypt p + checkPassphrase EncryptWithScrypt p' hp + `shouldBe` Left ErrWrongPassphrase diff --git a/lib/shelley/src/Cardano/Wallet/Shelley/Transaction.hs b/lib/shelley/src/Cardano/Wallet/Shelley/Transaction.hs index 773f12309fe..c05bcda1a5b 100644 --- a/lib/shelley/src/Cardano/Wallet/Shelley/Transaction.hs +++ b/lib/shelley/src/Cardano/Wallet/Shelley/Transaction.hs @@ -92,13 +92,15 @@ import Cardano.Wallet.CoinSelection , selectionDelta ) import Cardano.Wallet.Primitive.AddressDerivation - ( Depth (..), Passphrase (..), RewardAccount (..), WalletKey (..) ) + ( Depth (..), RewardAccount (..), WalletKey (..) ) import Cardano.Wallet.Primitive.AddressDerivation.Byron ( ByronKey ) import Cardano.Wallet.Primitive.AddressDerivation.Icarus ( IcarusKey ) import Cardano.Wallet.Primitive.AddressDerivation.Shelley ( ShelleyKey, toRewardAccountRaw ) +import Cardano.Wallet.Primitive.Passphrase + ( Passphrase (..) ) import Cardano.Wallet.Primitive.Slotting ( PastHorizonException, TimeInterpreter, getSystemStart, toEpochInfo ) import Cardano.Wallet.Primitive.Types diff --git a/lib/shelley/test/unit/Cardano/Wallet/Shelley/TransactionSpec.hs b/lib/shelley/test/unit/Cardano/Wallet/Shelley/TransactionSpec.hs index c8f20721a30..84861f349aa 100644 --- a/lib/shelley/test/unit/Cardano/Wallet/Shelley/TransactionSpec.hs +++ b/lib/shelley/test/unit/Cardano/Wallet/Shelley/TransactionSpec.hs @@ -97,10 +97,6 @@ import Cardano.Wallet.Primitive.AddressDerivation , Depth (..) , DerivationIndex (..) , NetworkDiscriminant (..) - , Passphrase (..) - , PassphraseMaxLength (..) - , PassphraseMinLength (..) - , PassphraseScheme (..) , deriveRewardAccount , getRawKey , hex @@ -119,6 +115,13 @@ import Cardano.Wallet.Primitive.AddressDiscovery.Sequential ( SeqState, defaultAddressPoolGap, mkSeqStateFromRootXPrv, purposeCIP1852 ) import Cardano.Wallet.Primitive.Model ( Wallet (..), unsafeInitWallet ) +import Cardano.Wallet.Primitive.Passphrase + ( Passphrase (..) + , PassphraseMaxLength (..) + , PassphraseMinLength (..) + , PassphraseScheme (..) + , preparePassphrase + ) import Cardano.Wallet.Primitive.Slotting ( TimeInterpreter, hoistTimeInterpreter, mkSingleEraInterpreter ) import Cardano.Wallet.Primitive.Types From e56beef525f5da3a94471751a0cd437a549efe53 Mon Sep 17 00:00:00 2001 From: Rodney Lorrimar Date: Mon, 2 Aug 2021 02:22:55 +0800 Subject: [PATCH 03/15] Fixes --- .../src/Test/Integration/Framework/DSL.hs | 16 ++++++--------- .../Integration/Scenario/API/Byron/Wallets.hs | 6 +++--- .../Scenario/API/Shared/Wallets.hs | 4 +++- .../test/unit/Cardano/Wallet/DB/Arbitrary.hs | 18 +++++++++-------- .../unit/Cardano/Wallet/DB/StateMachine.hs | 16 +++++++-------- .../AddressDerivation/MintBurnSpec.hs | 3 ++- .../Wallet/Primitive/AddressDerivationSpec.hs | 20 +++++++++++-------- .../Wallet/Primitive/PassphraseSpec.hs | 6 +++--- lib/core/test/unit/Cardano/WalletSpec.hs | 8 ++++---- .../Cardano/Wallet/Shelley/TransactionSpec.hs | 8 ++++---- 10 files changed, 55 insertions(+), 50 deletions(-) diff --git a/lib/core-integration/src/Test/Integration/Framework/DSL.hs b/lib/core-integration/src/Test/Integration/Framework/DSL.hs index 62afaa1fe51..de99278e4a1 100644 --- a/lib/core-integration/src/Test/Integration/Framework/DSL.hs +++ b/lib/core-integration/src/Test/Integration/Framework/DSL.hs @@ -279,8 +279,6 @@ import Cardano.Wallet.Primitive.AddressDerivation , WalletKey (..) , fromHex , hex -import Cardano.Wallet.Primitive.Passphrase ( Passphrase (..) - , preparePassphrase ) import Cardano.Wallet.Primitive.AddressDerivation.Byron ( ByronKey (..) ) @@ -292,6 +290,10 @@ import Cardano.Wallet.Primitive.AddressDiscovery.Sequential ( coinTypeAda ) import Cardano.Wallet.Primitive.AddressDiscovery.Shared ( CredentialType (..) ) +import Cardano.Wallet.Primitive.Passphrase + ( Passphrase (..), preparePassphrase ) +import Cardano.Wallet.Primitive.Passphrase.Legacy + ( encryptPassphraseTestingOnly ) import Cardano.Wallet.Primitive.SyncProgress ( SyncProgress (..) ) import Cardano.Wallet.Primitive.Types @@ -343,14 +345,10 @@ import Crypto.Hash ( Blake2b_160, Digest, digestFromByteString ) import Crypto.Hash.Utils ( blake2b224 ) -import Crypto.Random.Entropy - ( getEntropy ) import Data.Aeson ( FromJSON, ToJSON, Value, (.=) ) import Data.Aeson.QQ ( aesonQQ ) -import Data.ByteArray.Encoding - ( Base (..), convertFromBase, convertToBase ) import Data.ByteString ( ByteString ) import Data.Either.Extra @@ -455,15 +453,13 @@ import qualified Cardano.Wallet.Primitive.AddressDerivation.Byron as Byron import qualified Cardano.Wallet.Primitive.AddressDerivation.Icarus as Icarus import qualified Cardano.Wallet.Primitive.AddressDerivation.Shared as Shared import qualified Cardano.Wallet.Primitive.AddressDerivation.Shelley as Shelley +import qualified Cardano.Wallet.Primitive.Passphrase.Types as W import qualified Cardano.Wallet.Primitive.Types as W import qualified Cardano.Wallet.Primitive.Types.Coin as Coin import qualified Cardano.Wallet.Primitive.Types.TokenBundle as TokenBundle import qualified Cardano.Wallet.Primitive.Types.TokenMap as TokenMap import qualified Cardano.Wallet.Primitive.Types.TokenQuantity as TokenQuantity import qualified Codec.Binary.Bech32 as Bech32 -import qualified Codec.CBOR.Encoding as CBOR -import qualified Codec.CBOR.Write as CBOR -import qualified Crypto.KDF.Scrypt as Scrypt import qualified Data.Aeson as Aeson import qualified Data.ByteArray as BA import qualified Data.ByteString as BS @@ -1284,7 +1280,7 @@ emptyRandomWalletWithPasswd ctx rawPwd = do $ hex $ Byron.getKey $ Byron.generateKeyFromSeed seed pwd - pwdH <- encryptPassphraseTestingOnly pwd + pwdH <- liftIO $ toText <$> encryptPassphraseTestingOnly pwd emptyByronWalletFromXPrvWith ctx "random" ("Random Wallet", key, pwdH) postWallet' diff --git a/lib/core-integration/src/Test/Integration/Scenario/API/Byron/Wallets.hs b/lib/core-integration/src/Test/Integration/Scenario/API/Byron/Wallets.hs index 4b14a649d9c..a6df7a4b8bb 100644 --- a/lib/core-integration/src/Test/Integration/Scenario/API/Byron/Wallets.hs +++ b/lib/core-integration/src/Test/Integration/Scenario/API/Byron/Wallets.hs @@ -26,11 +26,13 @@ import Cardano.Wallet.Api.Types , WalletStyle (..) ) import Cardano.Wallet.Primitive.AddressDerivation - ( PassphraseMaxLength (..), PassphraseMinLength (..), PaymentAddress ) + ( PaymentAddress ) import Cardano.Wallet.Primitive.AddressDerivation.Byron ( ByronKey ) import Cardano.Wallet.Primitive.AddressDerivation.Icarus ( IcarusKey ) +import Cardano.Wallet.Primitive.Passphrase + ( PassphraseMaxLength (..), PassphraseMinLength (..) ) import Cardano.Wallet.Primitive.SyncProgress ( SyncProgress (..) ) import Control.Monad @@ -63,7 +65,6 @@ import Test.Integration.Framework.DSL , emptyIcarusWallet , emptyRandomWallet , emptyRandomWalletWithPasswd - , encryptWalletPasswordWithScrypt , eventually , expectErrorMessage , expectField @@ -79,7 +80,6 @@ import Test.Integration.Framework.DSL , fixtureRandomWallet , genMnemonics , getFromResponse - , getSaltFromHexScryptPassword , json , listFilteredByronWallets , postByronWallet diff --git a/lib/core-integration/src/Test/Integration/Scenario/API/Shared/Wallets.hs b/lib/core-integration/src/Test/Integration/Scenario/API/Shared/Wallets.hs index 9fdb21e4c9b..3aac19a5f42 100644 --- a/lib/core-integration/src/Test/Integration/Scenario/API/Shared/Wallets.hs +++ b/lib/core-integration/src/Test/Integration/Scenario/API/Shared/Wallets.hs @@ -39,9 +39,11 @@ import Cardano.Wallet.Api.Types import Cardano.Wallet.Compat ( (^?) ) import Cardano.Wallet.Primitive.AddressDerivation - ( DerivationIndex (..), Passphrase (..), Role (..) ) + ( DerivationIndex (..), Role (..) ) import Cardano.Wallet.Primitive.AddressDiscovery.Shared ( CredentialType (..) ) +import Cardano.Wallet.Primitive.Passphrase + ( Passphrase (..) ) import Cardano.Wallet.Primitive.SyncProgress ( SyncProgress (..) ) import Control.Monad diff --git a/lib/core/test/unit/Cardano/Wallet/DB/Arbitrary.hs b/lib/core/test/unit/Cardano/Wallet/DB/Arbitrary.hs index a5ddfaf6a88..e085a080abf 100644 --- a/lib/core/test/unit/Cardano/Wallet/DB/Arbitrary.hs +++ b/lib/core/test/unit/Cardano/Wallet/DB/Arbitrary.hs @@ -77,12 +77,12 @@ import Cardano.Wallet.Primitive.AddressDiscovery.Shared ( SharedState (..) ) import Cardano.Wallet.Primitive.Model ( Wallet, currentTip, getState, unsafeInitWallet, utxo ) -import Cardano.Wallet.Primitive.Passphrase - ( Passphrase (..) ) import Cardano.Wallet.Primitive.Passphrase.Types - ( Passphrase (..), PassphraseScheme (..), WalletPassphraseInfo (..) ) -import Cardano.Wallet.Primitive.Passphrase.Types - ( PassphraseHash (..) ) + ( Passphrase (..) + , PassphraseHash (..) + , PassphraseScheme (..) + , WalletPassphraseInfo (..) + ) import Cardano.Wallet.Primitive.Types ( Block (..) , BlockHeader (..) @@ -767,8 +767,10 @@ instance Arbitrary RewardAccount where RewardAccount . BS.pack <$> vector 28 instance Arbitrary (Hash purpose) where - arbitrary = do - Hash . convertToBase Base16 . BS.pack <$> vector 16 + arbitrary = Hash . convertToBase Base16 . BS.pack <$> vector 16 + +instance Arbitrary PassphraseHash where + arbitrary = PassphraseHash . convertToBase Base16 . BS.pack <$> vector 16 instance Arbitrary PoolId where arbitrary = do @@ -809,7 +811,7 @@ instance Buildable (ShelleyKey depth XPrv, PassphraseHash) where build (_, h) = tupleF (xprvF, prefixF 8 hF <> "..." <> suffixF 8 hF) where xprvF = "XPrv" :: Builder - hF = build (toText (Hash @"BlockHeader" (getPassphraseHash h))) + hF = build (toText (Hash @"BlockHeader" (BA.convert h))) instance Buildable MockChain where build (MockChain chain) = blockListF' mempty build chain diff --git a/lib/core/test/unit/Cardano/Wallet/DB/StateMachine.hs b/lib/core/test/unit/Cardano/Wallet/DB/StateMachine.hs index 9bd95c3f7e8..db592201ff4 100644 --- a/lib/core/test/unit/Cardano/Wallet/DB/StateMachine.hs +++ b/lib/core/test/unit/Cardano/Wallet/DB/StateMachine.hs @@ -319,27 +319,27 @@ class PersistPrivateKey k => MockPrivKey k where -> (k XPrv, PassphraseHash) -- | Unstuff the DBLayer private key into the mock type. - toMockPrivKey - :: (k XPrv, PassphraseHash) - -> MPrivKey - toMockPrivKey (_, PassphraseHash h) = - B8.unpack h + toMockPrivKey :: (k XPrv, PassphraseHash) -> MPrivKey + toMockPrivKey (_, h) = B8.unpack (BA.convert h) zeroes :: ByteString zeroes = B8.replicate 256 '0' instance MockPrivKey (ShelleyKey 'RootK) where - fromMockPrivKey s = (k, BA.convert (B8.pack s)) + fromMockPrivKey s = (k, unMockPrivKeyHash s) where (k, _) = unsafeDeserializeXPrv (zeroes, mempty) instance MockPrivKey (SharedKey 'RootK) where - fromMockPrivKey s = (k, BA.convert (B8.pack s)) + fromMockPrivKey s = (k, unMockPrivKeyHash s) where (k, _) = unsafeDeserializeXPrv (zeroes, mempty) instance MockPrivKey (ByronKey 'RootK) where - fromMockPrivKey s = (k, BA.convert (B8.pack s)) + fromMockPrivKey s = (k, unMockPrivKeyHash s) where (k, _) = unsafeDeserializeXPrv (zeroes <> ":", mempty) +unMockPrivKeyHash :: MPrivKey -> PassphraseHash +unMockPrivKeyHash = PassphraseHash . BA.convert . B8.pack + unMockTxId :: HasCallStack => Hash "Tx" -> SealedTx unMockTxId = mockSealedTx . getHash diff --git a/lib/core/test/unit/Cardano/Wallet/Primitive/AddressDerivation/MintBurnSpec.hs b/lib/core/test/unit/Cardano/Wallet/Primitive/AddressDerivation/MintBurnSpec.hs index e98dfcf5aaf..175ce91d06e 100644 --- a/lib/core/test/unit/Cardano/Wallet/Primitive/AddressDerivation/MintBurnSpec.hs +++ b/lib/core/test/unit/Cardano/Wallet/Primitive/AddressDerivation/MintBurnSpec.hs @@ -20,7 +20,6 @@ import Cardano.Wallet.Primitive.AddressDerivation ( Depth (..) , DerivationType (..) , Index (..) - , Passphrase , WalletKey (publicKey) , getRawKey , hashVerificationKey @@ -32,6 +31,8 @@ import Cardano.Wallet.Primitive.AddressDerivation.Shelley ( ShelleyKey ) import Cardano.Wallet.Primitive.AddressDerivationSpec () +import Cardano.Wallet.Primitive.Passphrase + ( Passphrase ) import Cardano.Wallet.Unsafe ( unsafeBech32Decode, unsafeFromHex, unsafeMkMnemonic, unsafeXPrv ) import Data.Function diff --git a/lib/core/test/unit/Cardano/Wallet/Primitive/AddressDerivationSpec.hs b/lib/core/test/unit/Cardano/Wallet/Primitive/AddressDerivationSpec.hs index 45191f544ed..0205c91fd61 100644 --- a/lib/core/test/unit/Cardano/Wallet/Primitive/AddressDerivationSpec.hs +++ b/lib/core/test/unit/Cardano/Wallet/Primitive/AddressDerivationSpec.hs @@ -37,7 +37,11 @@ import Cardano.Wallet.Primitive.AddressDerivation.Icarus import Cardano.Wallet.Primitive.AddressDerivation.Shelley ( ShelleyKey (..) ) import Cardano.Wallet.Primitive.Passphrase - ( checkPassphrase, encryptPassphrase, preparePassphrase ) + ( PassphraseHash (..) + , checkPassphrase + , encryptPassphrase + , preparePassphrase + ) import Cardano.Wallet.Primitive.Passphrase.Types ( ErrWrongPassphrase (..) , Passphrase (..) @@ -280,12 +284,12 @@ instance Arbitrary (Index 'WholeDomain 'AccountK) where shrink _ = [] arbitrary = arbitraryBoundedEnum -instance Arbitrary (Passphrase "raw") where +instance Arbitrary (Passphrase "user") where arbitrary = do n <- choose (passphraseMinLength p, passphraseMaxLength p) bytes <- T.encodeUtf8 . T.pack <$> replicateM n arbitraryPrintableChar return $ Passphrase $ BA.convert bytes - where p = Proxy :: Proxy "raw" + where p = Proxy :: Proxy "user" shrink (Passphrase bytes) | BA.length bytes <= passphraseMinLength p = [] @@ -295,11 +299,11 @@ instance Arbitrary (Passphrase "raw") where $ B8.take (passphraseMinLength p) $ BA.convert bytes ] - where p = Proxy :: Proxy "raw" + where p = Proxy :: Proxy "user" instance Arbitrary (Passphrase "encryption") where arbitrary = preparePassphrase EncryptWithPBKDF2 - <$> arbitrary @(Passphrase "raw") + <$> arbitrary @(Passphrase "user") instance {-# OVERLAPS #-} Arbitrary (Passphrase "generation") where shrink (Passphrase "") = [] @@ -309,11 +313,11 @@ instance {-# OVERLAPS #-} Arbitrary (Passphrase "generation") where InfiniteList bytes _ <- arbitrary return $ Passphrase $ BA.convert $ BS.pack $ take n bytes -instance Arbitrary (Hash "encryption") where +instance Arbitrary PassphraseHash where shrink _ = [] arbitrary = do InfiniteList bytes _ <- arbitrary - return $ Hash $ BS.pack $ take 32 bytes + pure $ PassphraseHash $ BA.convert $ BS.pack $ take 32 bytes instance Arbitrary PassphraseScheme where arbitrary = genericArbitrary @@ -422,7 +426,7 @@ instance Arbitrary SomeMnemonic where -- p = 1 -- These parameters are in Scrypt.defaultParams encryptPasswordWithScrypt - :: Passphrase "raw" + :: Passphrase "user" -> IO (Hash "encryption") encryptPasswordWithScrypt p = do hashed <- Scrypt.encryptPassIO Scrypt.defaultParams diff --git a/lib/core/test/unit/Cardano/Wallet/Primitive/PassphraseSpec.hs b/lib/core/test/unit/Cardano/Wallet/Primitive/PassphraseSpec.hs index 5c26f42622e..16f3e3665fa 100644 --- a/lib/core/test/unit/Cardano/Wallet/Primitive/PassphraseSpec.hs +++ b/lib/core/test/unit/Cardano/Wallet/Primitive/PassphraseSpec.hs @@ -102,7 +102,7 @@ spec = do \414941366a515867386539493d" checkPassphrase EncryptWithScrypt pwd hash `shouldBe` Right () it "compare new implementation with cardano-sl - normal password" $ do - let pwd = Passphrase @"raw" $ BA.convert $ T.encodeUtf8 "Secure Passphrase" + let pwd = Passphrase @"user" $ BA.convert $ T.encodeUtf8 "Secure Passphrase" let hash = Hash $ unsafeFromHex "31347c387c317c714968506842665966555a336f5156434c384449744b\ \677642417a6c584d62314d6d4267695433776a556f3d7c53672b436e30\ @@ -111,7 +111,7 @@ spec = do \524331385958326c6863552f36687a38432f496172773d3d" checkPassphrase EncryptWithScrypt pwd hash `shouldBe` Right () it "compare new implementation with cardano-sl - empty password" $ do - let pwd = Passphrase @"raw" $ BA.convert $ T.encodeUtf8 "" + let pwd = Passphrase @"user" $ BA.convert $ T.encodeUtf8 "" let hash = Hash $ unsafeFromHex "31347c387c317c5743424875746242496c6a66734d764934314a30727a7\ \9663076657375724954796376766a793150554e377452673d3d7c54753\ @@ -119,7 +119,7 @@ spec = do \2507657382b5135356a38303d" checkPassphrase EncryptWithScrypt pwd hash `shouldBe` Right () it "compare new implementation with cardano-sl - cardano-wallet password" $ do - let pwd = Passphrase @"raw" $ BA.convert $ T.encodeUtf8 "cardano-wallet" + let pwd = Passphrase @"user" $ BA.convert $ T.encodeUtf8 "cardano-wallet" let hash = Hash $ unsafeFromHex "31347c387c317c2b6a6f747446495a6a566d586f43374c6c54425a576c\ \597a425834515177666475467578436b4d485569733d7c78324d646738\ diff --git a/lib/core/test/unit/Cardano/WalletSpec.hs b/lib/core/test/unit/Cardano/WalletSpec.hs index af59fdfe6a0..7b5cd5bae84 100644 --- a/lib/core/test/unit/Cardano/WalletSpec.hs +++ b/lib/core/test/unit/Cardano/WalletSpec.hs @@ -556,7 +556,7 @@ walletUpdateNameNoSuchWallet wallet@(wid', _, _) wid wName = walletUpdatePassphrase :: (WalletId, WalletName, DummyState) - -> Passphrase "raw" + -> Passphrase "user" -> Maybe (ShelleyKey 'RootK XPrv, Passphrase "encryption") -> Property walletUpdatePassphrase wallet new mxprv = monadicIO $ do @@ -578,7 +578,7 @@ walletUpdatePassphrase wallet new mxprv = monadicIO $ do walletUpdatePassphraseWrong :: (WalletId, WalletName, DummyState) -> (ShelleyKey 'RootK XPrv, Passphrase "encryption") - -> (Passphrase "raw", Passphrase "raw") + -> (Passphrase "user", Passphrase "user") -> Property walletUpdatePassphraseWrong wallet (xprv, pwd) (old, new) = pwd /= coerce old ==> monadicIO $ do @@ -594,7 +594,7 @@ walletUpdatePassphraseWrong wallet (xprv, pwd) (old, new) = walletUpdatePassphraseNoSuchWallet :: (WalletId, WalletName, DummyState) -> WalletId - -> (Passphrase "raw", Passphrase "raw") + -> (Passphrase "user", Passphrase "user") -> Property walletUpdatePassphraseNoSuchWallet wallet@(wid', _, _) wid (old, new) = wid /= wid' ==> monadicIO $ do @@ -627,7 +627,7 @@ walletUpdatePassphraseDate wallet (xprv, pwd) = monadicIO $ liftIO $ do walletKeyIsReencrypted :: (WalletId, WalletName) -> (ShelleyKey 'RootK XPrv, Passphrase "encryption") - -> Passphrase "raw" + -> Passphrase "user" -> Property walletKeyIsReencrypted (_wid, _wname) (_xprv, _pwd) _newPwd = property True diff --git a/lib/shelley/test/unit/Cardano/Wallet/Shelley/TransactionSpec.hs b/lib/shelley/test/unit/Cardano/Wallet/Shelley/TransactionSpec.hs index 84861f349aa..18718a89411 100644 --- a/lib/shelley/test/unit/Cardano/Wallet/Shelley/TransactionSpec.hs +++ b/lib/shelley/test/unit/Cardano/Wallet/Shelley/TransactionSpec.hs @@ -1839,12 +1839,12 @@ instance Show XPrv where instance Eq XPrv where (==) = (==) `on` xprvToBytes -instance Arbitrary (Passphrase "raw") where +instance Arbitrary (Passphrase "user") where arbitrary = do n <- choose (passphraseMinLength p, passphraseMaxLength p) bytes <- T.encodeUtf8 . T.pack <$> replicateM n arbitraryPrintableChar return $ Passphrase $ BA.convert bytes - where p = Proxy :: Proxy "raw" + where p = Proxy :: Proxy "user" shrink (Passphrase bytes) | BA.length bytes <= passphraseMinLength p = [] @@ -1854,11 +1854,11 @@ instance Arbitrary (Passphrase "raw") where $ B8.take (passphraseMinLength p) $ BA.convert bytes ] - where p = Proxy :: Proxy "raw" + where p = Proxy :: Proxy "user" instance Arbitrary (Passphrase "encryption") where arbitrary = preparePassphrase EncryptWithPBKDF2 - <$> arbitrary @(Passphrase "raw") + <$> arbitrary @(Passphrase "user") instance Arbitrary (Quantity "byte" Word16) where arbitrary = Quantity <$> choose (128, 2048) From d423fe0f0777ed61d48cca4982bc4c77f296ab2f Mon Sep 17 00:00:00 2001 From: Hamish Mackenzie Date: Tue, 19 Oct 2021 15:31:33 +1300 Subject: [PATCH 04/15] Fix conversion error --- lib/core/src/Cardano/Wallet/Primitive/Passphrase/Legacy.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/core/src/Cardano/Wallet/Primitive/Passphrase/Legacy.hs b/lib/core/src/Cardano/Wallet/Primitive/Passphrase/Legacy.hs index 505a3d034b4..19cae6cf893 100644 --- a/lib/core/src/Cardano/Wallet/Primitive/Passphrase/Legacy.hs +++ b/lib/core/src/Cardano/Wallet/Primitive/Passphrase/Legacy.hs @@ -61,7 +61,7 @@ import Crypto.Scrypt checkPassphrase :: Passphrase "encryption" -> PassphraseHash -> Maybe Bool checkPassphrase pwd stored = Just $ verifyPass' pass encryptedPass where - pass = Pass pwd + pass = Pass (BA.convert pwd) encryptedPass = EncryptedPass (BA.convert stored) #else -- | Stub function for when compiled without @scrypt@. From af09329782926ffb90bdcdc536d098a6df6819ad Mon Sep 17 00:00:00 2001 From: Hamish Mackenzie Date: Tue, 19 Oct 2021 15:47:23 +1300 Subject: [PATCH 05/15] Turn off scrypt for aarch64 --- nix/haskell.nix | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/nix/haskell.nix b/nix/haskell.nix index cf0790475e9..a3b52c6fd77 100644 --- a/nix/haskell.nix +++ b/nix/haskell.nix @@ -421,6 +421,11 @@ haskell-nix: haskell-nix.stackProject' [ packages.cardano-addresses-cli.cabal-generator = lib.mkForce null; } + # Disable scrypt support on ARM64 + ({ pkgs, ... }: { + packages.cardano-wallet-core.flags.scrypt = !pkgs.stdenv.hostPlatform.isAarch64; + }) + # Allow installation of a newer version of Win32 than what is # included with GHC. The packages in this list are all those # installed with GHC, except for Win32. From 330e5097b5ce791e837f534b88c77b2778ed2820 Mon Sep 17 00:00:00 2001 From: Hamish Mackenzie Date: Wed, 20 Oct 2021 15:24:17 +1300 Subject: [PATCH 06/15] Fix missing install_name_tool and work around code signing issue --- nix/release-package.nix | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/nix/release-package.nix b/nix/release-package.nix index 7a2879fd1a1..f2b0d134657 100644 --- a/nix/release-package.nix +++ b/nix/release-package.nix @@ -35,10 +35,10 @@ in pkgs.stdenv.mkDerivation { inherit name; buildInputs = with pkgs.buildPackages; [ - binutils iohk-nix-utils nix ] + ++ (if pkgs.stdenv.hostPlatform.isDarwin then [ darwin.binutils ] else [ binutils ]) ++ lib.optionals makeTarball [ gnutar gzip ] ++ lib.optionals makeZip [ zip ]; checkInputs = with pkgs.buildPackages; [ @@ -63,7 +63,9 @@ pkgs.stdenv.mkDerivation { '' + lib.optionalString isMacOS '' # Rewrite library paths to standard non-nix locations - ( cd $name; rewrite-libs . `ls -1 | grep -Fv .dylib` ) + ( cd $name; rewrite-libs . `ls -1 | grep -Fv .dylib` + for a in *; do /usr/bin/codesign -f -s - $a; done + ) '' + lib.optionalString (isLinux || isMacOS) '' mkdir -p $name/auto-completion/{bash,zsh,fish} From 75fb0c812ce48cdfcfe8b20464282277018b3da4 Mon Sep 17 00:00:00 2001 From: Hamish Mackenzie Date: Wed, 20 Oct 2021 17:26:59 +1300 Subject: [PATCH 07/15] Fixes for cardano-wallet-core-integration --- .../src/Test/Integration/Scenario/API/Shelley/Wallets.hs | 4 ++-- .../src/Test/Integration/Scenario/CLI/Byron/Wallets.hs | 2 +- .../src/Test/Integration/Scenario/CLI/Shelley/Wallets.hs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/core-integration/src/Test/Integration/Scenario/API/Shelley/Wallets.hs b/lib/core-integration/src/Test/Integration/Scenario/API/Shelley/Wallets.hs index 8eca83d659e..cbea2e189f6 100644 --- a/lib/core-integration/src/Test/Integration/Scenario/API/Shelley/Wallets.hs +++ b/lib/core-integration/src/Test/Integration/Scenario/API/Shelley/Wallets.hs @@ -34,8 +34,6 @@ import Cardano.Wallet.Api.Types ) import Cardano.Wallet.Primitive.AddressDerivation ( DerivationIndex (..) - , PassphraseMaxLength (..) - , PassphraseMinLength (..) , PaymentAddress , Role (..) ) @@ -47,6 +45,8 @@ import Cardano.Wallet.Primitive.AddressDerivation.Shelley ( ShelleyKey ) import Cardano.Wallet.Primitive.AddressDiscovery.Sequential ( AddressPoolGap (..) ) +import Cardano.Wallet.Primitive.Passphrase + ( PassphraseMaxLength (..), PassphraseMinLength (..) ) import Cardano.Wallet.Primitive.SyncProgress ( SyncProgress (..) ) import Cardano.Wallet.Primitive.Types diff --git a/lib/core-integration/src/Test/Integration/Scenario/CLI/Byron/Wallets.hs b/lib/core-integration/src/Test/Integration/Scenario/CLI/Byron/Wallets.hs index ef3c6a55379..142f05c76c7 100644 --- a/lib/core-integration/src/Test/Integration/Scenario/CLI/Byron/Wallets.hs +++ b/lib/core-integration/src/Test/Integration/Scenario/CLI/Byron/Wallets.hs @@ -15,7 +15,7 @@ import Prelude import Cardano.Wallet.Api.Types ( ApiByronWallet, ApiUtxoStatistics, DecodeAddress ) -import Cardano.Wallet.Primitive.AddressDerivation +import Cardano.Wallet.Primitive.Passphrase ( PassphraseMaxLength (..), PassphraseMinLength (..) ) import Cardano.Wallet.Primitive.SyncProgress ( SyncProgress (..) ) diff --git a/lib/core-integration/src/Test/Integration/Scenario/CLI/Shelley/Wallets.hs b/lib/core-integration/src/Test/Integration/Scenario/CLI/Shelley/Wallets.hs index 3b65d5b2012..77dfd60d56e 100644 --- a/lib/core-integration/src/Test/Integration/Scenario/CLI/Shelley/Wallets.hs +++ b/lib/core-integration/src/Test/Integration/Scenario/CLI/Shelley/Wallets.hs @@ -26,10 +26,10 @@ import Cardano.Wallet.Api.Types , EncodeAddress (..) , getApiT ) -import Cardano.Wallet.Primitive.AddressDerivation - ( PassphraseMaxLength (..), PassphraseMinLength (..) ) import Cardano.Wallet.Primitive.AddressDiscovery.Sequential ( AddressPoolGap (..) ) +import Cardano.Wallet.Primitive.Passphrase + ( PassphraseMaxLength (..), PassphraseMinLength (..) ) import Cardano.Wallet.Primitive.SyncProgress ( SyncProgress (..) ) import Cardano.Wallet.Primitive.Types From 7f271f8bb2afac8b96ef69b20848222f999cdee8 Mon Sep 17 00:00:00 2001 From: Johannes Lund Date: Wed, 9 Mar 2022 16:34:18 +0100 Subject: [PATCH 08/15] Rebase and warning fixups --- .../src/Test/Integration/Framework/DSL.hs | 1 - .../Scenario/API/Shelley/Wallets.hs | 5 +- lib/core/src/Cardano/Wallet/DB/Sqlite.hs | 23 +---- .../src/Cardano/Wallet/DB/Sqlite/Migration.hs | 4 +- .../Wallet/Primitive/AddressDerivation.hs | 1 - .../Cardano/Wallet/Primitive/Passphrase.hs | 1 - .../Wallet/Primitive/Passphrase/Types.hs | 1 - .../test/unit/Cardano/Wallet/DB/Arbitrary.hs | 2 - .../Wallet/Primitive/AddressDerivationSpec.hs | 40 +-------- .../Wallet/Primitive/AddressDiscoverySpec.hs | 13 ++- .../Wallet/Primitive/Passphrase/LegacySpec.hs | 11 ++- .../Wallet/Primitive/PassphraseSpec.hs | 85 +++++++++++-------- lib/core/test/unit/Cardano/WalletSpec.hs | 26 +++--- lib/shelley/bench/restore-bench.hs | 4 +- .../Cardano/Wallet/Shelley/TransactionSpec.hs | 1 - 15 files changed, 92 insertions(+), 126 deletions(-) diff --git a/lib/core-integration/src/Test/Integration/Framework/DSL.hs b/lib/core-integration/src/Test/Integration/Framework/DSL.hs index de99278e4a1..703e679ca8f 100644 --- a/lib/core-integration/src/Test/Integration/Framework/DSL.hs +++ b/lib/core-integration/src/Test/Integration/Framework/DSL.hs @@ -454,7 +454,6 @@ import qualified Cardano.Wallet.Primitive.AddressDerivation.Icarus as Icarus import qualified Cardano.Wallet.Primitive.AddressDerivation.Shared as Shared import qualified Cardano.Wallet.Primitive.AddressDerivation.Shelley as Shelley import qualified Cardano.Wallet.Primitive.Passphrase.Types as W -import qualified Cardano.Wallet.Primitive.Types as W import qualified Cardano.Wallet.Primitive.Types.Coin as Coin import qualified Cardano.Wallet.Primitive.Types.TokenBundle as TokenBundle import qualified Cardano.Wallet.Primitive.Types.TokenMap as TokenMap diff --git a/lib/core-integration/src/Test/Integration/Scenario/API/Shelley/Wallets.hs b/lib/core-integration/src/Test/Integration/Scenario/API/Shelley/Wallets.hs index cbea2e189f6..a6edc92676f 100644 --- a/lib/core-integration/src/Test/Integration/Scenario/API/Shelley/Wallets.hs +++ b/lib/core-integration/src/Test/Integration/Scenario/API/Shelley/Wallets.hs @@ -33,10 +33,7 @@ import Cardano.Wallet.Api.Types , WalletStyle (..) ) import Cardano.Wallet.Primitive.AddressDerivation - ( DerivationIndex (..) - , PaymentAddress - , Role (..) - ) + ( DerivationIndex (..), PaymentAddress, Role (..) ) import Cardano.Wallet.Primitive.AddressDerivation.Byron ( ByronKey ) import Cardano.Wallet.Primitive.AddressDerivation.Icarus diff --git a/lib/core/src/Cardano/Wallet/DB/Sqlite.hs b/lib/core/src/Cardano/Wallet/DB/Sqlite.hs index 3cd4ab16f3d..c60f5255dd9 100644 --- a/lib/core/src/Cardano/Wallet/DB/Sqlite.hs +++ b/lib/core/src/Cardano/Wallet/DB/Sqlite.hs @@ -123,28 +123,7 @@ import Cardano.Wallet.DB.WalletState , getSlot ) import Cardano.Wallet.Primitive.AddressDerivation - ( Depth (..) - , DerivationType (..) - , Index (..) - , MkKeyFingerprint (..) - , NetworkDiscriminant (..) - , PaymentAddress (..) - , PersistPrivateKey (..) - , PersistPublicKey (..) - , Role (..) - , SoftDerivation (..) - , WalletKey (..) - ) -import Cardano.Wallet.Primitive.AddressDerivation.Icarus - ( IcarusKey ) -import Cardano.Wallet.Primitive.AddressDerivation.SharedKey - ( SharedKey (..) ) -import Cardano.Wallet.Primitive.AddressDerivation.Shelley - ( ShelleyKey (..) ) -import Cardano.Wallet.Primitive.AddressDiscovery - ( GetPurpose ) -import Cardano.Wallet.Primitive.AddressDiscovery.Shared - ( CredentialType (..) ) + ( Depth (..), PersistPrivateKey (..), WalletKey (..) ) import Cardano.Wallet.Primitive.Passphrase ( PassphraseHash ) import Cardano.Wallet.Primitive.Slotting diff --git a/lib/core/src/Cardano/Wallet/DB/Sqlite/Migration.hs b/lib/core/src/Cardano/Wallet/DB/Sqlite/Migration.hs index 513ae75e95e..d0d7e3771df 100644 --- a/lib/core/src/Cardano/Wallet/DB/Sqlite/Migration.hs +++ b/lib/core/src/Cardano/Wallet/DB/Sqlite/Migration.hs @@ -39,6 +39,8 @@ import Cardano.Wallet.Primitive.AddressDerivation.Icarus ( IcarusKey ) import Cardano.Wallet.Primitive.AddressDerivation.Shelley ( ShelleyKey (..) ) +import Cardano.Wallet.Primitive.Passphrase.Types + ( PassphraseScheme (..) ) import Control.Monad ( forM_, void, when ) import Control.Tracer @@ -360,7 +362,7 @@ migrateManually tr proxy defaultFieldValues = assignDefaultPassphraseScheme conn -- loop to apply case below ColumnPresent -> do value <- either (fail . show) (\x -> pure $ "\"" <> x <> "\"") $ - fromPersistValueText (toPersistValue W.EncryptWithPBKDF2) + fromPersistValueText (toPersistValue EncryptWithPBKDF2) traceWith tr . MsgExpectedMigration $ MsgManualMigrationNeeded passphraseScheme value query <- Sqlite.prepare conn $ T.unwords diff --git a/lib/core/src/Cardano/Wallet/Primitive/AddressDerivation.hs b/lib/core/src/Cardano/Wallet/Primitive/AddressDerivation.hs index f52446969ff..40dfaaf0433 100644 --- a/lib/core/src/Cardano/Wallet/Primitive/AddressDerivation.hs +++ b/lib/core/src/Cardano/Wallet/Primitive/AddressDerivation.hs @@ -5,7 +5,6 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GADTs #-} -{-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE PolyKinds #-} diff --git a/lib/core/src/Cardano/Wallet/Primitive/Passphrase.hs b/lib/core/src/Cardano/Wallet/Primitive/Passphrase.hs index ed8cf5ec6ed..409b106ae9f 100644 --- a/lib/core/src/Cardano/Wallet/Primitive/Passphrase.hs +++ b/lib/core/src/Cardano/Wallet/Primitive/Passphrase.hs @@ -1,7 +1,6 @@ {-# LANGUAGE DataKinds #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE TupleSections #-} -{-# LANGUAGE TypeApplications #-} -- | -- Copyright: © 2018-2021 IOHK diff --git a/lib/core/src/Cardano/Wallet/Primitive/Passphrase/Types.hs b/lib/core/src/Cardano/Wallet/Primitive/Passphrase/Types.hs index 4eb7927eadc..e47aa250867 100644 --- a/lib/core/src/Cardano/Wallet/Primitive/Passphrase/Types.hs +++ b/lib/core/src/Cardano/Wallet/Primitive/Passphrase/Types.hs @@ -5,7 +5,6 @@ {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE RoleAnnotations #-} {-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} -- | diff --git a/lib/core/test/unit/Cardano/Wallet/DB/Arbitrary.hs b/lib/core/test/unit/Cardano/Wallet/DB/Arbitrary.hs index e085a080abf..776c080942e 100644 --- a/lib/core/test/unit/Cardano/Wallet/DB/Arbitrary.hs +++ b/lib/core/test/unit/Cardano/Wallet/DB/Arbitrary.hs @@ -150,8 +150,6 @@ import Crypto.Hash ( hash ) import Data.ByteArray.Encoding ( Base (Base16), convertToBase ) -import Data.Coerce - ( coerce ) import Data.Functor.Identity ( Identity (..) ) import Data.Generics.Internal.VL.Lens diff --git a/lib/core/test/unit/Cardano/Wallet/Primitive/AddressDerivationSpec.hs b/lib/core/test/unit/Cardano/Wallet/Primitive/AddressDerivationSpec.hs index 0205c91fd61..12cd394127b 100644 --- a/lib/core/test/unit/Cardano/Wallet/Primitive/AddressDerivationSpec.hs +++ b/lib/core/test/unit/Cardano/Wallet/Primitive/AddressDerivationSpec.hs @@ -37,27 +37,15 @@ import Cardano.Wallet.Primitive.AddressDerivation.Icarus import Cardano.Wallet.Primitive.AddressDerivation.Shelley ( ShelleyKey (..) ) import Cardano.Wallet.Primitive.Passphrase - ( PassphraseHash (..) - , checkPassphrase - , encryptPassphrase - , preparePassphrase - ) + ( PassphraseHash (..), preparePassphrase ) import Cardano.Wallet.Primitive.Passphrase.Types - ( ErrWrongPassphrase (..) - , Passphrase (..) - , PassphraseHash (..) + ( Passphrase (..) , PassphraseMaxLength (..) , PassphraseMinLength (..) , PassphraseScheme (..) ) -import Cardano.Wallet.Primitive.Types.Hash - ( Hash (..) ) -import Cardano.Wallet.Unsafe - ( unsafeFromHex ) import Control.Monad ( replicateM ) -import Control.Monad.IO.Class - ( liftIO ) import Data.Either ( isRight ) import Data.Proxy @@ -81,12 +69,9 @@ import Test.QuickCheck , property , (.&&.) , (===) - , (==>) ) import Test.QuickCheck.Arbitrary.Generic ( genericArbitrary ) -import Test.QuickCheck.Monadic - ( monadicIO ) import Test.Text.Roundtrip ( textRoundtrip ) @@ -94,9 +79,6 @@ import qualified Cardano.Crypto.Wallet as CC import qualified Cardano.Wallet.Primitive.AddressDerivation.Byron as Byron import qualified Cardano.Wallet.Primitive.AddressDerivation.Icarus as Icarus import qualified Cardano.Wallet.Primitive.AddressDerivation.Shelley as Shelley -import qualified Codec.CBOR.Encoding as CBOR -import qualified Codec.CBOR.Write as CBOR -import qualified Crypto.Scrypt as Scrypt import qualified Data.ByteArray as BA import qualified Data.ByteString as BS import qualified Data.ByteString.Char8 as B8 @@ -419,21 +401,3 @@ genPassphrase range = do instance Arbitrary SomeMnemonic where arbitrary = SomeMnemonic <$> genMnemonic @12 - --- | Encrypt password using Scrypt function with the following parameters: --- logN = 14 --- r = 8 --- p = 1 --- These parameters are in Scrypt.defaultParams -encryptPasswordWithScrypt - :: Passphrase "user" - -> IO (Hash "encryption") -encryptPasswordWithScrypt p = do - hashed <- Scrypt.encryptPassIO Scrypt.defaultParams - $ Scrypt.Pass - $ CBOR.toStrictByteString - $ CBOR.encodeBytes - $ BA.convert passwd - pure $ Hash $ Scrypt.getEncryptedPass hashed - where - (Passphrase passwd) = preparePassphrase EncryptWithScrypt p diff --git a/lib/core/test/unit/Cardano/Wallet/Primitive/AddressDiscoverySpec.hs b/lib/core/test/unit/Cardano/Wallet/Primitive/AddressDiscoverySpec.hs index aefed4a775f..6dc83195438 100644 --- a/lib/core/test/unit/Cardano/Wallet/Primitive/AddressDiscoverySpec.hs +++ b/lib/core/test/unit/Cardano/Wallet/Primitive/AddressDiscoverySpec.hs @@ -33,8 +33,13 @@ import Cardano.Wallet.Primitive.AddressDiscovery ( IsOurs (..), knownAddresses ) import Cardano.Wallet.Primitive.AddressDiscovery.Random ( mkRndState ) -import Cardano.Wallet.Primitive.Passphrase.Types - ( Passphrase (..), passphraseMaxLength, passphraseMinLength ) +import Cardano.Wallet.Primitive.Passphrase + ( Passphrase (..) + , PassphraseScheme (EncryptWithPBKDF2) + , passphraseMaxLength + , passphraseMinLength + , preparePassphrase + ) import Control.Monad ( replicateM ) import Data.Maybe @@ -110,6 +115,10 @@ instance Arbitrary (ByronKey 'RootK XPrv) where shrink _ = [] arbitrary = genRootKeys +instance Arbitrary (Passphrase "encryption") where + arbitrary = preparePassphrase EncryptWithPBKDF2 + <$> arbitrary @(Passphrase "user") + genRootKeys :: Gen (ByronKey 'RootK XPrv) genRootKeys = do mnemonic <- arbitrary diff --git a/lib/core/test/unit/Cardano/Wallet/Primitive/Passphrase/LegacySpec.hs b/lib/core/test/unit/Cardano/Wallet/Primitive/Passphrase/LegacySpec.hs index 9a592a82e1a..88bbc8ab8c9 100644 --- a/lib/core/test/unit/Cardano/Wallet/Primitive/Passphrase/LegacySpec.hs +++ b/lib/core/test/unit/Cardano/Wallet/Primitive/Passphrase/LegacySpec.hs @@ -1,13 +1,18 @@ +{-# LANGUAGE DataKinds #-} module Cardano.Wallet.Primitive.Passphrase.LegacySpec ( spec ) where import Prelude -import Test.Hspec (Spec, it, describe, shouldBe) -import Cardano.Wallet.Unsafe (unsafeFromHex) import Cardano.Wallet.Primitive.Passphrase.Legacy -import Cardano.Wallet.Primitive.Passphrase.Types (Passphrase (..), PassphraseHash (..)) + ( checkPassphraseTestingOnly, getSalt, preparePassphrase ) +import Cardano.Wallet.Primitive.Passphrase.Types + ( Passphrase (..), PassphraseHash (..) ) +import Cardano.Wallet.Unsafe + ( unsafeFromHex ) +import Test.Hspec + ( Spec, describe, it, shouldBe ) import qualified Data.ByteArray as BA diff --git a/lib/core/test/unit/Cardano/Wallet/Primitive/PassphraseSpec.hs b/lib/core/test/unit/Cardano/Wallet/Primitive/PassphraseSpec.hs index 16f3e3665fa..e7d34f683b8 100644 --- a/lib/core/test/unit/Cardano/Wallet/Primitive/PassphraseSpec.hs +++ b/lib/core/test/unit/Cardano/Wallet/Primitive/PassphraseSpec.hs @@ -14,63 +14,47 @@ module Cardano.Wallet.Primitive.PassphraseSpec import Prelude -import Cardano.Wallet.Gen - ( genMnemonic ) import Cardano.Wallet.Primitive.Passphrase ( ErrWrongPassphrase (..) , Passphrase (..) + , PassphraseHash (..) , PassphraseMaxLength (..) , PassphraseMinLength (..) , PassphraseScheme (..) , checkPassphrase , encryptPassphrase + , encryptPassphrase' , preparePassphrase ) -import Cardano.Wallet.Primitive.Types.Hash - ( Hash (..) ) import Cardano.Wallet.Unsafe ( unsafeFromHex ) import Control.Monad ( replicateM ) import Control.Monad.IO.Class ( liftIO ) -import Data.Either - ( isRight ) import Data.Proxy ( Proxy (..) ) import Test.Hspec - ( Spec, describe, it, shouldBe, shouldSatisfy ) + ( Spec, describe, it, shouldBe ) import Test.Hspec.Extra ( parallel ) import Test.QuickCheck ( Arbitrary (..) - , Gen - , InfiniteList (..) , Property - , arbitraryBoundedEnum , arbitraryPrintableChar - , arbitrarySizedBoundedIntegral , choose - , expectFailure - , genericShrink - , oneof , property - , (.&&.) , (===) , (==>) ) import Test.QuickCheck.Arbitrary.Generic ( genericArbitrary ) import Test.QuickCheck.Monadic - ( monadicIO ) + ( assert, monadicIO, run ) import Test.Text.Roundtrip ( textRoundtrip ) -import qualified Codec.CBOR.Encoding as CBOR -import qualified Codec.CBOR.Write as CBOR -import qualified Crypto.Scrypt as Scrypt import qualified Data.ByteArray as BA -import qualified Data.ByteString as BS import qualified Data.ByteString.Char8 as B8 import qualified Data.Text as T import qualified Data.Text.Encoding as T @@ -78,7 +62,7 @@ import qualified Data.Text.Encoding as T spec :: Spec spec = do parallel $ describe "Text Roundtrip" $ do - textRoundtrip $ Proxy @(Passphrase "encryption") + textRoundtrip $ Proxy @(Passphrase "user") parallel $ describe "Passphrases" $ do it "checkPassphrase p h(p) == Right ()" $ @@ -95,7 +79,7 @@ spec = do parallel $ describe "golden test legacy passphrase encryption" $ do it "compare new implementation with cardano-sl - short password" $ do let pwd = Passphrase $ BA.convert $ T.encodeUtf8 "patate" - let hash = PassphraseHash $ unsafeFromHex + let hash = PassphraseHash $ BA.convert $ unsafeFromHex "31347c387c317c574342652b796362417576356c2b4258676a344a314c\ \6343675375414c2f5653393661364e576a2b7550766655513d3d7c2f37\ \6738486c59723174734e394f6e4e753253302b6a65515a6b5437316b45\ @@ -103,7 +87,7 @@ spec = do checkPassphrase EncryptWithScrypt pwd hash `shouldBe` Right () it "compare new implementation with cardano-sl - normal password" $ do let pwd = Passphrase @"user" $ BA.convert $ T.encodeUtf8 "Secure Passphrase" - let hash = Hash $ unsafeFromHex + let hash = PassphraseHash $ BA.convert $ unsafeFromHex "31347c387c317c714968506842665966555a336f5156434c384449744b\ \677642417a6c584d62314d6d4267695433776a556f3d7c53672b436e30\ \4232766b4475682f704265335569694577633364385845756f55737661\ @@ -112,7 +96,7 @@ spec = do checkPassphrase EncryptWithScrypt pwd hash `shouldBe` Right () it "compare new implementation with cardano-sl - empty password" $ do let pwd = Passphrase @"user" $ BA.convert $ T.encodeUtf8 "" - let hash = Hash $ unsafeFromHex + let hash = PassphraseHash $ BA.convert $ unsafeFromHex "31347c387c317c5743424875746242496c6a66734d764934314a30727a7\ \9663076657375724954796376766a793150554e377452673d3d7c54753\ \434596d6e547957546c5759674a3164494f7974474a7842632b432f786\ @@ -120,7 +104,7 @@ spec = do checkPassphrase EncryptWithScrypt pwd hash `shouldBe` Right () it "compare new implementation with cardano-sl - cardano-wallet password" $ do let pwd = Passphrase @"user" $ BA.convert $ T.encodeUtf8 "cardano-wallet" - let hash = Hash $ unsafeFromHex + let hash = PassphraseHash $ BA.convert $ unsafeFromHex "31347c387c317c2b6a6f747446495a6a566d586f43374c6c54425a576c\ \597a425834515177666475467578436b4d485569733d7c78324d646738\ \49554a3232507235676531393575445a76583646552b7757395a6a6a2f\ @@ -134,7 +118,7 @@ spec = do -------------------------------------------------------------------------------} prop_passphraseRoundtrip - :: Passphrase "encryption" + :: Passphrase "user" -> Property prop_passphraseRoundtrip pwd = monadicIO $ liftIO $ do (scheme, hpwd) <- encryptPassphrase pwd @@ -142,35 +126,64 @@ prop_passphraseRoundtrip pwd = monadicIO $ liftIO $ do checkPassphrase EncryptWithPBKDF2 pwd hpwd `shouldBe` Right () prop_passphraseRoundtripFail - :: Passphrase "encryption" - -> Passphrase "encryption" + :: Passphrase "user" + -> Passphrase "user" -> Property prop_passphraseRoundtripFail p p' = p /= p' ==> monadicIO $ do - (_scheme, hp) <- runIO $ encryptPassphrase p - checkPassphrase EncryptWithPBKDF2 p' hp - `shouldBe` Left ErrWrongPassphrase + (_scheme, hp) <- run $ encryptPassphrase p + assert $ checkPassphrase EncryptWithPBKDF2 p' hp + == Left ErrWrongPassphrase prop_passphraseHashMalformed :: PassphraseScheme - -> Passphrase "encryption" + -> Passphrase "user" -> Property prop_passphraseHashMalformed scheme pwd = - checkPassphrase scheme pwd (Hash mempty) `shouldBe` Left ErrWrongPassphrase + checkPassphrase scheme pwd (PassphraseHash mempty) === Left ErrWrongPassphrase prop_passphraseFromScryptRoundtrip - :: Passphrase "encryption" + :: Passphrase "user" -> Property prop_passphraseFromScryptRoundtrip p = monadicIO $ liftIO $ do hp <- encryptPasswordWithScrypt p checkPassphrase EncryptWithScrypt p hp `shouldBe` Right () prop_passphraseFromScryptRoundtripFail - :: Passphrase "encryption" - -> Passphrase "encryption" + :: Passphrase "user" + -> Passphrase "user" -> Property prop_passphraseFromScryptRoundtripFail p p' = p /= p' ==> monadicIO $ liftIO $ do hp <- encryptPasswordWithScrypt p checkPassphrase EncryptWithScrypt p' hp `shouldBe` Left ErrWrongPassphrase + +encryptPasswordWithScrypt + :: Passphrase "user" + -> IO PassphraseHash +encryptPasswordWithScrypt = encryptPassphrase' EncryptWithScrypt + +instance Arbitrary (Passphrase "user") where + arbitrary = do + n <- choose (passphraseMinLength p, passphraseMaxLength p) + bytes <- T.encodeUtf8 . T.pack <$> replicateM n arbitraryPrintableChar + return $ Passphrase $ BA.convert bytes + where p = Proxy :: Proxy "user" + + shrink (Passphrase bytes) + | BA.length bytes <= passphraseMinLength p = [] + | otherwise = + [ Passphrase + $ BA.convert + $ B8.take (passphraseMinLength p) + $ BA.convert bytes + ] + where p = Proxy :: Proxy "user" + +instance Arbitrary PassphraseScheme where + arbitrary = genericArbitrary + +instance Arbitrary (Passphrase "encryption") where + arbitrary = preparePassphrase EncryptWithPBKDF2 + <$> arbitrary @(Passphrase "user") diff --git a/lib/core/test/unit/Cardano/WalletSpec.hs b/lib/core/test/unit/Cardano/WalletSpec.hs index 7b5cd5bae84..3617d405bc1 100644 --- a/lib/core/test/unit/Cardano/WalletSpec.hs +++ b/lib/core/test/unit/Cardano/WalletSpec.hs @@ -68,10 +68,8 @@ import Cardano.Wallet.Primitive.AddressDerivation ( Depth (..) , DerivationIndex (..) , DerivationType (..) - , ErrWrongPassphrase (..) , HardDerivation (..) , Index - , Passphrase (..) , Role (..) , publicKey ) @@ -86,6 +84,10 @@ import Cardano.Wallet.Primitive.AddressDiscovery ) import Cardano.Wallet.Primitive.Migration.SelectionSpec ( MockTxConstraints (..), genTokenBundleMixed, unMockTxConstraints ) +import Cardano.Wallet.Primitive.Passphrase + ( ErrWrongPassphrase (..), Passphrase (..) ) +import Cardano.Wallet.Primitive.Passphrase.Current + ( preparePassphrase ) import Cardano.Wallet.Primitive.SyncProgress ( SyncTolerance (..) ) import Cardano.Wallet.Primitive.Types @@ -119,6 +121,10 @@ import Cardano.Wallet.Primitive.Types.RewardAccount ( RewardAccount (..) ) import Cardano.Wallet.Primitive.Types.TokenBundle ( TokenBundle (TokenBundle), getAssets ) +import Cardano.Wallet.Primitive.Types.TokenMap.Gen + ( genAssetIdLargeRange ) +import Cardano.Wallet.Primitive.Types.TokenQuantity.Gen + ( genTokenQuantityPositive ) import Cardano.Wallet.Primitive.Types.Tx ( Direction (..) , LocalTxSubmissionStatus (..) @@ -278,10 +284,6 @@ import qualified Cardano.Wallet.Primitive.Migration as Migration import qualified Cardano.Wallet.Primitive.Types.Coin as Coin import qualified Cardano.Wallet.Primitive.Types.TokenBundle as TokenBundle import qualified Cardano.Wallet.Primitive.Types.TokenMap as TokenMap -import Cardano.Wallet.Primitive.Types.TokenMap.Gen - ( genAssetIdLargeRange ) -import Cardano.Wallet.Primitive.Types.TokenQuantity.Gen - ( genTokenQuantityPositive ) import qualified Data.ByteArray as BA import qualified Data.ByteString as BS import qualified Data.ByteString.Char8 as B8 @@ -557,7 +559,7 @@ walletUpdateNameNoSuchWallet wallet@(wid', _, _) wid wName = walletUpdatePassphrase :: (WalletId, WalletName, DummyState) -> Passphrase "user" - -> Maybe (ShelleyKey 'RootK XPrv, Passphrase "encryption") + -> Maybe (ShelleyKey 'RootK XPrv, Passphrase "user") -> Property walletUpdatePassphrase wallet new mxprv = monadicIO $ do WalletLayerFixture _ wl [wid] _ <- run $ setupFixture wallet @@ -577,7 +579,7 @@ walletUpdatePassphrase wallet new mxprv = monadicIO $ do walletUpdatePassphraseWrong :: (WalletId, WalletName, DummyState) - -> (ShelleyKey 'RootK XPrv, Passphrase "encryption") + -> (ShelleyKey 'RootK XPrv, Passphrase "user") -> (Passphrase "user", Passphrase "user") -> Property walletUpdatePassphraseWrong wallet (xprv, pwd) (old, new) = @@ -605,7 +607,7 @@ walletUpdatePassphraseNoSuchWallet wallet@(wid', _, _) wid (old, new) = walletUpdatePassphraseDate :: (WalletId, WalletName, DummyState) - -> (ShelleyKey 'RootK XPrv, Passphrase "encryption") + -> (ShelleyKey 'RootK XPrv, Passphrase "user") -> Property walletUpdatePassphraseDate wallet (xprv, pwd) = monadicIO $ liftIO $ do (WalletLayerFixture _ wl [wid] _) <- liftIO $ setupFixture wallet @@ -626,7 +628,7 @@ walletUpdatePassphraseDate wallet (xprv, pwd) = monadicIO $ liftIO $ do walletKeyIsReencrypted :: (WalletId, WalletName) - -> (ShelleyKey 'RootK XPrv, Passphrase "encryption") + -> (ShelleyKey 'RootK XPrv, Passphrase "user") -> Passphrase "user" -> Property walletKeyIsReencrypted (_wid, _wname) (_xprv, _pwd) _newPwd = property True @@ -1480,13 +1482,13 @@ instance Arbitrary (Passphrase purpose) where instance Arbitrary SomeMnemonic where arbitrary = SomeMnemonic <$> genMnemonic @12 -instance {-# OVERLAPS #-} Arbitrary (ShelleyKey 'RootK XPrv, Passphrase "encryption") +instance {-# OVERLAPS #-} Arbitrary (ShelleyKey 'RootK XPrv, Passphrase "user") where shrink _ = [] arbitrary = do pwd <- arbitrary mw <- arbitrary - let key = generateKeyFromSeed (mw, Nothing) pwd + let key = generateKeyFromSeed (mw, Nothing) (preparePassphrase pwd) return (key, pwd) instance Arbitrary EpochNo where diff --git a/lib/shelley/bench/restore-bench.hs b/lib/shelley/bench/restore-bench.hs index da14b3a759e..d472691e592 100644 --- a/lib/shelley/bench/restore-bench.hs +++ b/lib/shelley/bench/restore-bench.hs @@ -78,11 +78,11 @@ import Cardano.Wallet.Logging ( trMessageText ) import Cardano.Wallet.Network ( ChainFollowLog (..), ChainSyncLog (..), NetworkLayer (..) ) + import Cardano.Wallet.Primitive.AddressDerivation ( Depth (..) , NetworkDiscriminant (..) , NetworkDiscriminantVal (..) - , Passphrase (..) , PaymentAddress , PersistPrivateKey , WalletKey @@ -112,6 +112,8 @@ import Cardano.Wallet.Primitive.AddressDiscovery.Sequential ) import Cardano.Wallet.Primitive.Model ( Wallet, currentTip, getState, totalUTxO ) +import Cardano.Wallet.Primitive.Passphrase + ( Passphrase (..) ) import Cardano.Wallet.Primitive.Slotting ( TimeInterpreter, neverFails ) import Cardano.Wallet.Primitive.SyncProgress diff --git a/lib/shelley/test/unit/Cardano/Wallet/Shelley/TransactionSpec.hs b/lib/shelley/test/unit/Cardano/Wallet/Shelley/TransactionSpec.hs index 18718a89411..1d86a6cd541 100644 --- a/lib/shelley/test/unit/Cardano/Wallet/Shelley/TransactionSpec.hs +++ b/lib/shelley/test/unit/Cardano/Wallet/Shelley/TransactionSpec.hs @@ -102,7 +102,6 @@ import Cardano.Wallet.Primitive.AddressDerivation , hex , liftRawKey , paymentAddress - , preparePassphrase , publicKey ) import Cardano.Wallet.Primitive.AddressDerivation.Byron From eb3a58f54f1e784524d676b8a3e7c3a7c110aa76 Mon Sep 17 00:00:00 2001 From: Johannes Lund Date: Wed, 9 Mar 2022 20:09:27 +0100 Subject: [PATCH 09/15] Adapt passphrase tests to work without scrypt I simply moved them to LegacySpec, and ensured LegacySpec only ran when scrypt was set. However, I'm seeing the following failures with +scrypt set: test/unit/Cardano/Wallet/Primitive/Passphrase/LegacySpec.hs:53:9: 1) Cardano.Wallet.Primitive.Passphrase.Legacy, Scrypt tests-only cryptonite version, Verify passphrase expected: True but got: False To rerun use: --match "/Cardano.Wallet.Primitive.Passphrase.Legacy/Scrypt tests-only cryptonite version/Verify passphrase/" test/unit/Cardano/Wallet/Primitive/Passphrase/LegacySpec.hs:68:9: 2) Cardano.Wallet.Primitive.Passphrase.Legacy, Scrypt tests-only cryptonite version, getSalt expected: Just "abc" but got: Just "+jottFIZjVmXoC7LlTBZWlYzBX4QQwfduFuxCkMHUis=" To rerun use: --match "/Cardano.Wallet.Primitive.Passphrase.Legacy/Scrypt tests-only cryptonite version/getSalt/" test/unit/Cardano/Wallet/Primitive/Passphrase/LegacySpec.hs:84:13: 3) Cardano.Wallet.Primitive.Passphrase.Legacy, Scrypt tests-only cryptonite version, golden test legacy passphrase encryption, compare new implementation with cardano-sl - short password expected: Right () but got: Left ErrWrongPassphrase To rerun use: --match "/Cardano.Wallet.Primitive.Passphrase.Legacy/Scrypt tests-only cryptonite version/golden test legacy passphrase encryption/compare new implementation with cardano-sl - short password/" test/unit/Cardano/Wallet/Primitive/Passphrase/LegacySpec.hs:94:13: 4) Cardano.Wallet.Primitive.Passphrase.Legacy, Scrypt tests-only cryptonite version, golden test legacy passphrase encryption, compare new implementation with cardano-sl - normal password expected: Right () but got: Left ErrWrongPassphrase To rerun use: --match "/Cardano.Wallet.Primitive.Passphrase.Legacy/Scrypt tests-only cryptonite version/golden test legacy passphrase encryption/compare new implementation with cardano-sl - normal password/" test/unit/Cardano/Wallet/Primitive/Passphrase/LegacySpec.hs:103:13: 5) Cardano.Wallet.Primitive.Passphrase.Legacy, Scrypt tests-only cryptonite version, golden test legacy passphrase encryption, compare new implementation with cardano-sl - empty password expected: Right () but got: Left ErrWrongPassphrase To rerun use: --match "/Cardano.Wallet.Primitive.Passphrase.Legacy/Scrypt tests-only cryptonite version/golden test legacy passphrase encryption/compare new implementation with cardano-sl - empty password/" test/unit/Cardano/Wallet/Primitive/Passphrase/LegacySpec.hs:113:13: 6) Cardano.Wallet.Primitive.Passphrase.Legacy, Scrypt tests-only cryptonite version, golden test legacy passphrase encryption, compare new implementation with cardano-sl - cardano-wallet password expected: Right () but got: Left ErrWrongPassphrase To rerun use: --match "/Cardano.Wallet.Primitive.Passphrase.Legacy/Scrypt tests-only cryptonite version/golden test legacy passphrase encryption/compare new implementation with cardano-sl - cardano-wallet password/" Randomized with seed 866879004 Finished in 28.5483 seconds, used 40.2343 seconds of CPU time 85 examples, 6 failures --- lib/core/cardano-wallet-core.cabal | 1 + .../Wallet/Primitive/Passphrase/Gen.hs | 57 ++++++++ .../Wallet/Primitive/Passphrase/Legacy.hs | 9 ++ .../Wallet/Primitive/Passphrase/LegacySpec.hs | 130 +++++++++++++++++- .../Wallet/Primitive/PassphraseSpec.hs | 124 +++-------------- 5 files changed, 213 insertions(+), 108 deletions(-) create mode 100644 lib/core/src/Cardano/Wallet/Primitive/Passphrase/Gen.hs diff --git a/lib/core/cardano-wallet-core.cabal b/lib/core/cardano-wallet-core.cabal index 3cc407e68df..11f48886f85 100644 --- a/lib/core/cardano-wallet-core.cabal +++ b/lib/core/cardano-wallet-core.cabal @@ -230,6 +230,7 @@ library Cardano.Wallet.Primitive.SyncProgress Cardano.Wallet.Primitive.Passphrase Cardano.Wallet.Primitive.Passphrase.Current + Cardano.Wallet.Primitive.Passphrase.Gen Cardano.Wallet.Primitive.Passphrase.Legacy Cardano.Wallet.Primitive.Passphrase.Types Cardano.Wallet.Primitive.Types diff --git a/lib/core/src/Cardano/Wallet/Primitive/Passphrase/Gen.hs b/lib/core/src/Cardano/Wallet/Primitive/Passphrase/Gen.hs new file mode 100644 index 00000000000..ddc8dd10b1d --- /dev/null +++ b/lib/core/src/Cardano/Wallet/Primitive/Passphrase/Gen.hs @@ -0,0 +1,57 @@ +{-# LANGUAGE DataKinds #-} + +module Cardano.Wallet.Primitive.Passphrase.Gen + ( genUserPassphrase + , shrinkUserPassphrase + , genPassphraseScheme + , genEncryptionPassphrase + ) where + +import Prelude + +import Cardano.Wallet.Primitive.Passphrase + ( Passphrase (..) + , PassphraseMaxLength (..) + , PassphraseMinLength (..) + , PassphraseScheme (..) + , preparePassphrase + ) +import Control.Monad + ( replicateM ) +import Data.Proxy + ( Proxy (..) ) +import Test.QuickCheck + ( Gen, arbitraryPrintableChar, choose ) +import Test.QuickCheck.Arbitrary.Generic + ( genericArbitrary ) + +import qualified Data.ByteArray as BA +import qualified Data.ByteString.Char8 as B8 +import qualified Data.Text as T +import qualified Data.Text.Encoding as T + +genUserPassphrase :: Gen (Passphrase "user") +genUserPassphrase = do + n <- choose (passphraseMinLength p, passphraseMaxLength p) + bytes <- T.encodeUtf8 . T.pack <$> replicateM n arbitraryPrintableChar + return $ Passphrase $ BA.convert bytes + where p = Proxy :: Proxy "user" + +shrinkUserPassphrase :: Passphrase "user" -> [Passphrase "user"] +shrinkUserPassphrase (Passphrase bytes) + | BA.length bytes <= passphraseMinLength p = [] + | otherwise = + [ Passphrase + $ BA.convert + $ B8.take (passphraseMinLength p) + $ BA.convert bytes + ] + where p = Proxy :: Proxy "user" + +genPassphraseScheme :: Gen PassphraseScheme +genPassphraseScheme = genericArbitrary + +genEncryptionPassphrase :: Gen (Passphrase "encryption") +genEncryptionPassphrase = preparePassphrase EncryptWithPBKDF2 + <$> genUserPassphrase + diff --git a/lib/core/src/Cardano/Wallet/Primitive/Passphrase/Legacy.hs b/lib/core/src/Cardano/Wallet/Primitive/Passphrase/Legacy.hs index 19cae6cf893..295a8fef5a2 100644 --- a/lib/core/src/Cardano/Wallet/Primitive/Passphrase/Legacy.hs +++ b/lib/core/src/Cardano/Wallet/Primitive/Passphrase/Legacy.hs @@ -26,6 +26,9 @@ module Cardano.Wallet.Primitive.Passphrase.Legacy , checkPassphraseTestingOnly , encryptPassphraseTestingOnly + -- * Testing-only helper + , haveScrypt + -- * Internal functions , getSalt , genSalt @@ -63,10 +66,16 @@ checkPassphrase pwd stored = Just $ verifyPass' pass encryptedPass where pass = Pass (BA.convert pwd) encryptedPass = EncryptedPass (BA.convert stored) + +haveScrypt :: Bool +haveScrypt = True #else -- | Stub function for when compiled without @scrypt@. checkPassphrase :: Passphrase "encryption" -> PassphraseHash -> Maybe Bool checkPassphrase _ _ = Nothing + +haveScrypt :: Bool +haveScrypt = False #endif preparePassphrase :: Passphrase "user" -> Passphrase "encryption" diff --git a/lib/core/test/unit/Cardano/Wallet/Primitive/Passphrase/LegacySpec.hs b/lib/core/test/unit/Cardano/Wallet/Primitive/Passphrase/LegacySpec.hs index 88bbc8ab8c9..7d75530ae67 100644 --- a/lib/core/test/unit/Cardano/Wallet/Primitive/Passphrase/LegacySpec.hs +++ b/lib/core/test/unit/Cardano/Wallet/Primitive/Passphrase/LegacySpec.hs @@ -1,23 +1,54 @@ {-# LANGUAGE DataKinds #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE TypeApplications #-} + +{-# OPTIONS_GHC -fno-warn-orphans #-} + module Cardano.Wallet.Primitive.Passphrase.LegacySpec ( spec ) where import Prelude +import Cardano.Wallet.Primitive.Passphrase + ( ErrWrongPassphrase (ErrWrongPassphrase) + , PassphraseScheme (EncryptWithScrypt) + , checkPassphrase + , encryptPassphrase' + ) +import Cardano.Wallet.Primitive.Passphrase.Gen + ( genEncryptionPassphrase + , genPassphraseScheme + , genUserPassphrase + , shrinkUserPassphrase + ) import Cardano.Wallet.Primitive.Passphrase.Legacy - ( checkPassphraseTestingOnly, getSalt, preparePassphrase ) + ( checkPassphraseTestingOnly, getSalt, haveScrypt, preparePassphrase ) import Cardano.Wallet.Primitive.Passphrase.Types ( Passphrase (..), PassphraseHash (..) ) import Cardano.Wallet.Unsafe ( unsafeFromHex ) +import Control.Monad.IO.Class + ( liftIO ) +import Data.ByteString + ( ByteString ) +import GHC.Stack + ( HasCallStack ) import Test.Hspec - ( Spec, describe, it, shouldBe ) + ( Spec, before_, describe, it, pendingWith, shouldBe ) +import Test.Hspec.Extra + ( parallel ) +import Test.QuickCheck + ( Arbitrary (..), Property, property, (==>) ) +import Test.QuickCheck.Monadic + ( monadicIO ) import qualified Data.ByteArray as BA +import qualified Data.Text.Encoding as T + spec :: Spec -spec = describe "Scrypt tests-only cryptonite version" $ do +spec = onlyWithScrypt $ describe "Scrypt tests-only cryptonite version" $ do it "Verify passphrase" $ do checkPassphraseTestingOnly (preparePassphrase fixturePassphrase) fixturePassphraseEncrypted `shouldBe` True @@ -31,7 +62,61 @@ spec = describe "Scrypt tests-only cryptonite version" $ do checkPassphraseTestingOnly (preparePassphrase fixturePassphrase) fixturePassphraseEncryptedWrongHash `shouldBe` False it "getSalt" $ do - getSalt fixturePassphraseEncrypted `shouldBe` Just (Passphrase "abc") + let + unwrap :: Passphrase "salt" -> ByteString + unwrap (Passphrase x) = BA.convert x + unwrap <$> (getSalt fixturePassphraseEncrypted) `shouldBe` (Just "abc") + + it "checkPassphrase p h(p) == Right () for Scrypt passwords" $ + property prop_passphraseFromScryptRoundtrip + it "p /= p' => checkPassphrase p' h(p) == Left ErrWrongPassphrase for Scrypt passwords" $ + property prop_passphraseFromScryptRoundtripFail + + parallel $ describe "golden test legacy passphrase encryption" $ do + it "compare new implementation with cardano-sl - short password" $ do + let pwd = Passphrase $ BA.convert $ T.encodeUtf8 "patate" + let hash = PassphraseHash $ BA.convert $ unsafeFromHex $ mconcat + [ "31347c387c317c574342652b796362417576356c2b4258676a344a314c" + , "6343675375414c2f5653393661364e576a2b7550766655513d3d7c2f37" + , "6738486c59723174734e394f6e4e753253302b6a65515a6b5437316b45" + , "414941366a515867386539493d" + ] + checkPassphrase EncryptWithScrypt pwd hash `shouldBe` Right () + it "compare new implementation with cardano-sl - normal password" $ do + let pwd = Passphrase @"user" $ BA.convert $ T.encodeUtf8 "Secure Passphrase" + let hash = PassphraseHash $ BA.convert $ unsafeFromHex $ mconcat + [ "31347c387c317c714968506842665966555a336f5156434c384449744b" + , "677642417a6c584d62314d6d4267695433776a556f3d7c53672b436e30" + , "4232766b4475682f704265335569694577633364385845756f55737661" + , "42514e62464443353569474f4135736e453144326743346f47564c472b" + , "524331385958326c6863552f36687a38432f496172773d3d" + ] + checkPassphrase EncryptWithScrypt pwd hash `shouldBe` Right () + it "compare new implementation with cardano-sl - empty password" $ do + let pwd = Passphrase @"user" $ BA.convert $ T.encodeUtf8 "" + let hash = PassphraseHash $ BA.convert $ unsafeFromHex $ mconcat + [ "31347c387c317c5743424875746242496c6a66734d764934314a30727a7" + , "9663076657375724954796376766a793150554e377452673d3d7c54753" + , "434596d6e547957546c5759674a3164494f7974474a7842632b432f786" + , "2507657382b5135356a38303d" + ] + checkPassphrase EncryptWithScrypt pwd hash `shouldBe` Right () + it "compare new implementation with cardano-sl - cardano-wallet password" $ do + let pwd = Passphrase @"user" $ BA.convert $ T.encodeUtf8 "cardano-wallet" + let hash = PassphraseHash $ BA.convert $ unsafeFromHex $ mconcat + [ "31347c387c317c2b6a6f747446495a6a566d586f43374c6c54425a576c" + , "597a425834515177666475467578436b4d485569733d7c78324d646738" + , "49554a3232507235676531393575445a76583646552b7757395a6a6a2f" + , "51303054356c654751794279732f7662753367526d726c316c657a7150" + , "43676d364e6758476d4d2f4b6438343265304b4945773d3d" + ] + checkPassphrase EncryptWithScrypt pwd hash `shouldBe` Right () + +onlyWithScrypt :: HasCallStack => Spec -> Spec +onlyWithScrypt = + if haveScrypt + then id + else before_ (pendingWith "needs to be compiled with scrypt to run") -- | Default passphrase used for fixture wallets fixturePassphrase :: Passphrase "user" @@ -64,3 +149,40 @@ fixturePassphraseEncryptedWrongHash = PassphraseHash $ BA.convert $ unsafeFromHe \49554a3232507235676531393575445a76583646552b7757395a6a6a2f\ \51303054356c654751794279732f7662753367526d726c316c657a7150\ \43676d364e6758476d4d2f4b6438343265304b4945773d30" + +{------------------------------------------------------------------------------- + Properties +-------------------------------------------------------------------------------} + +prop_passphraseFromScryptRoundtrip + :: Passphrase "user" + -> Property +prop_passphraseFromScryptRoundtrip p = monadicIO $ liftIO $ do + hp <- encryptPasswordWithScrypt p + checkPassphrase EncryptWithScrypt p hp `shouldBe` Right () + +prop_passphraseFromScryptRoundtripFail + :: Passphrase "user" + -> Passphrase "user" + -> Property +prop_passphraseFromScryptRoundtripFail p p' = + p /= p' ==> monadicIO $ liftIO $ do + hp <- encryptPasswordWithScrypt p + checkPassphrase EncryptWithScrypt p' hp + `shouldBe` Left ErrWrongPassphrase + +encryptPasswordWithScrypt + :: Passphrase "user" + -> IO PassphraseHash +encryptPasswordWithScrypt = encryptPassphrase' EncryptWithScrypt + + +instance Arbitrary (Passphrase "user") where + arbitrary = genUserPassphrase + shrink = shrinkUserPassphrase + +instance Arbitrary PassphraseScheme where + arbitrary = genPassphraseScheme + +instance Arbitrary (Passphrase "encryption") where + arbitrary = genEncryptionPassphrase diff --git a/lib/core/test/unit/Cardano/Wallet/Primitive/PassphraseSpec.hs b/lib/core/test/unit/Cardano/Wallet/Primitive/PassphraseSpec.hs index e7d34f683b8..63acf0902a2 100644 --- a/lib/core/test/unit/Cardano/Wallet/Primitive/PassphraseSpec.hs +++ b/lib/core/test/unit/Cardano/Wallet/Primitive/PassphraseSpec.hs @@ -1,4 +1,5 @@ {-# LANGUAGE AllowAmbiguousTypes #-} +{-# LANGUAGE CPP #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE KindSignatures #-} @@ -18,18 +19,18 @@ import Cardano.Wallet.Primitive.Passphrase ( ErrWrongPassphrase (..) , Passphrase (..) , PassphraseHash (..) - , PassphraseMaxLength (..) - , PassphraseMinLength (..) , PassphraseScheme (..) , checkPassphrase , encryptPassphrase - , encryptPassphrase' - , preparePassphrase ) -import Cardano.Wallet.Unsafe - ( unsafeFromHex ) -import Control.Monad - ( replicateM ) +import Cardano.Wallet.Primitive.Passphrase.Gen + ( genEncryptionPassphrase + , genPassphraseScheme + , genUserPassphrase + , shrinkUserPassphrase + ) +import Cardano.Wallet.Primitive.Passphrase.Legacy + ( haveScrypt ) import Control.Monad.IO.Class ( liftIO ) import Data.Proxy @@ -39,25 +40,12 @@ import Test.Hspec import Test.Hspec.Extra ( parallel ) import Test.QuickCheck - ( Arbitrary (..) - , Property - , arbitraryPrintableChar - , choose - , property - , (===) - , (==>) - ) -import Test.QuickCheck.Arbitrary.Generic - ( genericArbitrary ) + ( Arbitrary (..), Property, counterexample, property, (===), (==>) ) import Test.QuickCheck.Monadic ( assert, monadicIO, run ) import Test.Text.Roundtrip ( textRoundtrip ) -import qualified Data.ByteArray as BA -import qualified Data.ByteString.Char8 as B8 -import qualified Data.Text as T -import qualified Data.Text.Encoding as T spec :: Spec spec = do @@ -71,47 +59,6 @@ spec = do property prop_passphraseRoundtripFail it "checkPassphrase fails when hash is malformed" $ property prop_passphraseHashMalformed - it "checkPassphrase p h(p) == Right () for Scrypt passwords" $ - property prop_passphraseFromScryptRoundtrip - it "p /= p' => checkPassphrase p' h(p) == Left ErrWrongPassphrase for Scrypt passwords" $ - property prop_passphraseFromScryptRoundtripFail - - parallel $ describe "golden test legacy passphrase encryption" $ do - it "compare new implementation with cardano-sl - short password" $ do - let pwd = Passphrase $ BA.convert $ T.encodeUtf8 "patate" - let hash = PassphraseHash $ BA.convert $ unsafeFromHex - "31347c387c317c574342652b796362417576356c2b4258676a344a314c\ - \6343675375414c2f5653393661364e576a2b7550766655513d3d7c2f37\ - \6738486c59723174734e394f6e4e753253302b6a65515a6b5437316b45\ - \414941366a515867386539493d" - checkPassphrase EncryptWithScrypt pwd hash `shouldBe` Right () - it "compare new implementation with cardano-sl - normal password" $ do - let pwd = Passphrase @"user" $ BA.convert $ T.encodeUtf8 "Secure Passphrase" - let hash = PassphraseHash $ BA.convert $ unsafeFromHex - "31347c387c317c714968506842665966555a336f5156434c384449744b\ - \677642417a6c584d62314d6d4267695433776a556f3d7c53672b436e30\ - \4232766b4475682f704265335569694577633364385845756f55737661\ - \42514e62464443353569474f4135736e453144326743346f47564c472b\ - \524331385958326c6863552f36687a38432f496172773d3d" - checkPassphrase EncryptWithScrypt pwd hash `shouldBe` Right () - it "compare new implementation with cardano-sl - empty password" $ do - let pwd = Passphrase @"user" $ BA.convert $ T.encodeUtf8 "" - let hash = PassphraseHash $ BA.convert $ unsafeFromHex - "31347c387c317c5743424875746242496c6a66734d764934314a30727a7\ - \9663076657375724954796376766a793150554e377452673d3d7c54753\ - \434596d6e547957546c5759674a3164494f7974474a7842632b432f786\ - \2507657382b5135356a38303d" - checkPassphrase EncryptWithScrypt pwd hash `shouldBe` Right () - it "compare new implementation with cardano-sl - cardano-wallet password" $ do - let pwd = Passphrase @"user" $ BA.convert $ T.encodeUtf8 "cardano-wallet" - let hash = PassphraseHash $ BA.convert $ unsafeFromHex - "31347c387c317c2b6a6f747446495a6a566d586f43374c6c54425a576c\ - \597a425834515177666475467578436b4d485569733d7c78324d646738\ - \49554a3232507235676531393575445a76583646552b7757395a6a6a2f\ - \51303054356c654751794279732f7662753367526d726c316c657a7150\ - \43676d364e6758476d4d2f4b6438343265304b4945773d3d" - checkPassphrase EncryptWithScrypt pwd hash `shouldBe` Right () - {------------------------------------------------------------------------------- Properties @@ -140,50 +87,19 @@ prop_passphraseHashMalformed -> Passphrase "user" -> Property prop_passphraseHashMalformed scheme pwd = - checkPassphrase scheme pwd (PassphraseHash mempty) === Left ErrWrongPassphrase - -prop_passphraseFromScryptRoundtrip - :: Passphrase "user" - -> Property -prop_passphraseFromScryptRoundtrip p = monadicIO $ liftIO $ do - hp <- encryptPasswordWithScrypt p - checkPassphrase EncryptWithScrypt p hp `shouldBe` Right () - -prop_passphraseFromScryptRoundtripFail - :: Passphrase "user" - -> Passphrase "user" - -> Property -prop_passphraseFromScryptRoundtripFail p p' = - p /= p' ==> monadicIO $ liftIO $ do - hp <- encryptPasswordWithScrypt p - checkPassphrase EncryptWithScrypt p' hp - `shouldBe` Left ErrWrongPassphrase - -encryptPasswordWithScrypt - :: Passphrase "user" - -> IO PassphraseHash -encryptPasswordWithScrypt = encryptPassphrase' EncryptWithScrypt + counterexample ("haveScrypt = " <> show haveScrypt) $ + checkPassphrase scheme pwd (PassphraseHash mempty) + === if not haveScrypt && scheme == EncryptWithScrypt + then Left $ ErrPassphraseSchemeUnsupported EncryptWithScrypt + else Left ErrWrongPassphrase instance Arbitrary (Passphrase "user") where - arbitrary = do - n <- choose (passphraseMinLength p, passphraseMaxLength p) - bytes <- T.encodeUtf8 . T.pack <$> replicateM n arbitraryPrintableChar - return $ Passphrase $ BA.convert bytes - where p = Proxy :: Proxy "user" - - shrink (Passphrase bytes) - | BA.length bytes <= passphraseMinLength p = [] - | otherwise = - [ Passphrase - $ BA.convert - $ B8.take (passphraseMinLength p) - $ BA.convert bytes - ] - where p = Proxy :: Proxy "user" + arbitrary = genUserPassphrase + shrink = shrinkUserPassphrase instance Arbitrary PassphraseScheme where - arbitrary = genericArbitrary + arbitrary = genPassphraseScheme instance Arbitrary (Passphrase "encryption") where - arbitrary = preparePassphrase EncryptWithPBKDF2 - <$> arbitrary @(Passphrase "user") + arbitrary = genEncryptionPassphrase + From 049e96c009e01637b392cb92f50e73a08b0e53f9 Mon Sep 17 00:00:00 2001 From: Johannes Lund Date: Thu, 17 Mar 2022 13:31:44 +0100 Subject: [PATCH 10/15] Correct Passphrase.Legacy implementation to make tests pass MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Swap order of cborify and hashMaybe This makes the legacy golden tests pass, and matches the old implementation. 2. Fix getSalt implementation The salt should be converted from base64. This fixes the test "Verify passphrase" calling `checkPassphraseTestingOnly`. The getSalt golden value was updated. With the "Verify passphrase" test now succeeding, it seems like the previous expected salt value — "abc" — was just a dummy value, and not the right expectation. --- .../Wallet/Primitive/Passphrase/Legacy.hs | 11 +++++--- .../Wallet/Primitive/Passphrase/LegacySpec.hs | 27 ++++++++++++++++--- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/lib/core/src/Cardano/Wallet/Primitive/Passphrase/Legacy.hs b/lib/core/src/Cardano/Wallet/Primitive/Passphrase/Legacy.hs index 295a8fef5a2..214f26d779d 100644 --- a/lib/core/src/Cardano/Wallet/Primitive/Passphrase/Legacy.hs +++ b/lib/core/src/Cardano/Wallet/Primitive/Passphrase/Legacy.hs @@ -43,7 +43,7 @@ import Crypto.Hash.Utils import Crypto.Random.Types ( MonadRandom (..) ) import Data.ByteArray.Encoding - ( Base (..), convertToBase ) + ( Base (..), convertFromBase, convertToBase ) import Data.ByteString ( ByteString ) import Data.Word @@ -54,6 +54,8 @@ import qualified Codec.CBOR.Write as CBOR import qualified Crypto.KDF.Scrypt as Scrypt import qualified Data.ByteArray as BA import qualified Data.ByteString.Char8 as B8 +import Data.Either.Extra + ( eitherToMaybe ) #if HAVE_SCRYPT import Crypto.Scrypt @@ -79,13 +81,13 @@ haveScrypt = False #endif preparePassphrase :: Passphrase "user" -> Passphrase "encryption" -preparePassphrase = Passphrase . hashMaybe . cborify . unPassphrase +preparePassphrase = Passphrase . cborify . hashMaybe . unPassphrase where hashMaybe pw | pw == mempty = mempty | otherwise = BA.convert $ blake2b256 pw - cborify = CBOR.toStrictByteString . CBOR.encodeBytes . BA.convert + cborify = BA.convert . CBOR.toStrictByteString . CBOR.encodeBytes -- | This is for use by test cases only. Use only the implementation from the -- @scrypt@ package for application code. @@ -98,7 +100,8 @@ checkPassphraseTestingOnly pwd stored = case getSalt stored of -- This will fail unless there are exactly 5 fields getSalt :: PassphraseHash -> Maybe (Passphrase "salt") getSalt (PassphraseHash stored) = case B8.split '|' (BA.convert stored) of - [_logN, _r, _p, salt, _passHash] -> Just $ Passphrase $ BA.convert salt + [_logN, _r, _p, salt, _passHash] -> eitherToMaybe $ + Passphrase <$> convertFromBase Base64 salt _ -> Nothing -- | This is for use by test cases only. diff --git a/lib/core/test/unit/Cardano/Wallet/Primitive/Passphrase/LegacySpec.hs b/lib/core/test/unit/Cardano/Wallet/Primitive/Passphrase/LegacySpec.hs index 7d75530ae67..440df340c54 100644 --- a/lib/core/test/unit/Cardano/Wallet/Primitive/Passphrase/LegacySpec.hs +++ b/lib/core/test/unit/Cardano/Wallet/Primitive/Passphrase/LegacySpec.hs @@ -23,7 +23,12 @@ import Cardano.Wallet.Primitive.Passphrase.Gen , shrinkUserPassphrase ) import Cardano.Wallet.Primitive.Passphrase.Legacy - ( checkPassphraseTestingOnly, getSalt, haveScrypt, preparePassphrase ) + ( checkPassphraseTestingOnly + , encryptPassphraseTestingOnly + , getSalt + , haveScrypt + , preparePassphrase + ) import Cardano.Wallet.Primitive.Passphrase.Types ( Passphrase (..), PassphraseHash (..) ) import Cardano.Wallet.Unsafe @@ -50,8 +55,20 @@ import qualified Data.Text.Encoding as T spec :: Spec spec = onlyWithScrypt $ describe "Scrypt tests-only cryptonite version" $ do it "Verify passphrase" $ do - checkPassphraseTestingOnly (preparePassphrase fixturePassphrase) - fixturePassphraseEncrypted `shouldBe` True + let Just salt = getSalt fixturePassphraseEncrypted + let + unwrap :: PassphraseHash -> ByteString + unwrap (PassphraseHash x) = BA.convert x + let x = encryptPassphraseTestingOnly + (preparePassphrase fixturePassphrase) + salt + unwrap x `shouldBe` unwrap fixturePassphraseEncrypted + -- The above results in a better error message on failure, but this is + -- the main thing we want to test: + checkPassphraseTestingOnly + (preparePassphrase fixturePassphrase) + fixturePassphraseEncrypted + `shouldBe` True it "Verify wrong passphrase" $ do checkPassphraseTestingOnly (preparePassphrase fixturePassphraseWrong) fixturePassphraseEncrypted `shouldBe` False @@ -65,7 +82,9 @@ spec = onlyWithScrypt $ describe "Scrypt tests-only cryptonite version" $ do let unwrap :: Passphrase "salt" -> ByteString unwrap (Passphrase x) = BA.convert x - unwrap <$> (getSalt fixturePassphraseEncrypted) `shouldBe` (Just "abc") + unwrap <$> (getSalt fixturePassphraseEncrypted) + `shouldBe` Just "\250:-\180R\EM\141Y\151\160.\203\149\&0YZV3\ENQ~\ + \\DLEC\a\221\184[\177\nC\aR+" it "checkPassphrase p h(p) == Right () for Scrypt passwords" $ property prop_passphraseFromScryptRoundtrip From 0e4d2a8fafd5b327ee7087b878751e04bae923a5 Mon Sep 17 00:00:00 2001 From: Johannes Lund Date: Fri, 18 Mar 2022 19:16:06 +0100 Subject: [PATCH 11/15] Tweaks to PassphraseSpec --- .../Wallet/Primitive/PassphraseSpec.hs | 27 ++++++++++++++----- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/lib/core/test/unit/Cardano/Wallet/Primitive/PassphraseSpec.hs b/lib/core/test/unit/Cardano/Wallet/Primitive/PassphraseSpec.hs index 63acf0902a2..f579977f71c 100644 --- a/lib/core/test/unit/Cardano/Wallet/Primitive/PassphraseSpec.hs +++ b/lib/core/test/unit/Cardano/Wallet/Primitive/PassphraseSpec.hs @@ -73,14 +73,15 @@ prop_passphraseRoundtrip pwd = monadicIO $ liftIO $ do checkPassphrase EncryptWithPBKDF2 pwd hpwd `shouldBe` Right () prop_passphraseRoundtripFail - :: Passphrase "user" + :: PassphraseScheme + -> Passphrase "user" -> Passphrase "user" -> Property -prop_passphraseRoundtripFail p p' = +prop_passphraseRoundtripFail scheme p p' = p /= p' ==> monadicIO $ do (_scheme, hp) <- run $ encryptPassphrase p - assert $ checkPassphrase EncryptWithPBKDF2 p' hp - == Left ErrWrongPassphrase + assert $ checkPassphrase scheme p' hp == + whenSupported scheme (Left ErrWrongPassphrase) prop_passphraseHashMalformed :: PassphraseScheme @@ -89,9 +90,7 @@ prop_passphraseHashMalformed prop_passphraseHashMalformed scheme pwd = counterexample ("haveScrypt = " <> show haveScrypt) $ checkPassphrase scheme pwd (PassphraseHash mempty) - === if not haveScrypt && scheme == EncryptWithScrypt - then Left $ ErrPassphraseSchemeUnsupported EncryptWithScrypt - else Left ErrWrongPassphrase + === whenSupported scheme (Left ErrWrongPassphrase) instance Arbitrary (Passphrase "user") where arbitrary = genUserPassphrase @@ -103,3 +102,17 @@ instance Arbitrary PassphraseScheme where instance Arbitrary (Passphrase "encryption") where arbitrary = genEncryptionPassphrase +-- | Helper that returns 'ErrPassphraseSchemeUnsupported' when the provided +-- scheme is 'EncryptWithScrypt' and 'haveScrypt' is false, and otherwise +-- returns the second argument. +whenSupported + :: PassphraseScheme + -> Either ErrWrongPassphrase () + -> Either ErrWrongPassphrase () +whenSupported EncryptWithPBKDF2 = id +whenSupported EncryptWithScrypt + | not haveScrypt + = const . Left $ ErrPassphraseSchemeUnsupported EncryptWithScrypt + | otherwise + = id + From a064897cced5c77601b3ab490d4a4d6179f97036 Mon Sep 17 00:00:00 2001 From: Johannes Lund Date: Tue, 22 Mar 2022 14:23:05 +0100 Subject: [PATCH 12/15] Move cborify outside preparePassphrase This way it matches the implemetnation on `master`. --- .../Cardano/Wallet/Primitive/Passphrase/Legacy.hs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/core/src/Cardano/Wallet/Primitive/Passphrase/Legacy.hs b/lib/core/src/Cardano/Wallet/Primitive/Passphrase/Legacy.hs index 214f26d779d..e22194d55d9 100644 --- a/lib/core/src/Cardano/Wallet/Primitive/Passphrase/Legacy.hs +++ b/lib/core/src/Cardano/Wallet/Primitive/Passphrase/Legacy.hs @@ -64,9 +64,9 @@ import Crypto.Scrypt -- | Verify a wallet spending password using the legacy Byron scrypt encryption -- scheme. checkPassphrase :: Passphrase "encryption" -> PassphraseHash -> Maybe Bool -checkPassphrase pwd stored = Just $ verifyPass' pass encryptedPass +checkPassphrase pwd stored = Just $ + verifyPass' (Pass (BA.convert (cborify pwd))) encryptedPass where - pass = Pass (BA.convert pwd) encryptedPass = EncryptedPass (BA.convert stored) haveScrypt :: Bool @@ -81,13 +81,12 @@ haveScrypt = False #endif preparePassphrase :: Passphrase "user" -> Passphrase "encryption" -preparePassphrase = Passphrase . cborify . hashMaybe . unPassphrase +preparePassphrase = Passphrase . hashMaybe . unPassphrase where hashMaybe pw | pw == mempty = mempty | otherwise = BA.convert $ blake2b256 pw - cborify = BA.convert . CBOR.toStrictByteString . CBOR.encodeBytes -- | This is for use by test cases only. Use only the implementation from the -- @scrypt@ package for application code. @@ -96,6 +95,10 @@ checkPassphraseTestingOnly pwd stored = case getSalt stored of Just salt -> encryptPassphraseTestingOnly pwd salt == stored Nothing -> False +cborify :: Passphrase "encryption" -> Passphrase "encryption" +cborify = Passphrase . BA.convert . CBOR.toStrictByteString + . CBOR.encodeBytes . BA.convert . unPassphrase + -- | Extract salt field from pipe-delimited password hash. -- This will fail unless there are exactly 5 fields getSalt :: PassphraseHash -> Maybe (Passphrase "salt") @@ -116,7 +119,7 @@ encryptPassphraseTestingOnly pwd = mkPassphraseHash <$> genSalt , convertToBase Base64 salt, convertToBase Base64 (passHash salt)] passHash :: Passphrase "salt" -> ByteString - passHash (Passphrase salt) = Scrypt.generate params pwd salt + passHash (Passphrase salt) = Scrypt.generate params (cborify pwd) salt params = Scrypt.Parameters ((2 :: Word64) ^ logN) r p 64 logN = 14 From 3fcd21fb8ca30ac98e7cbe8e1820265ff1009199 Mon Sep 17 00:00:00 2001 From: Johannes Lund Date: Thu, 24 Mar 2022 15:51:20 +0100 Subject: [PATCH 13/15] Add goldens for pbkdf2 passwords MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I manually tested these on current master — before the commits of this PR — and they passed. --- .../Wallet/Primitive/PassphraseSpec.hs | 59 ++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/lib/core/test/unit/Cardano/Wallet/Primitive/PassphraseSpec.hs b/lib/core/test/unit/Cardano/Wallet/Primitive/PassphraseSpec.hs index f579977f71c..476f80cb543 100644 --- a/lib/core/test/unit/Cardano/Wallet/Primitive/PassphraseSpec.hs +++ b/lib/core/test/unit/Cardano/Wallet/Primitive/PassphraseSpec.hs @@ -1,5 +1,4 @@ {-# LANGUAGE AllowAmbiguousTypes #-} -{-# LANGUAGE CPP #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE KindSignatures #-} @@ -22,6 +21,7 @@ import Cardano.Wallet.Primitive.Passphrase , PassphraseScheme (..) , checkPassphrase , encryptPassphrase + , preparePassphrase ) import Cardano.Wallet.Primitive.Passphrase.Gen ( genEncryptionPassphrase @@ -33,6 +33,8 @@ import Cardano.Wallet.Primitive.Passphrase.Legacy ( haveScrypt ) import Control.Monad.IO.Class ( liftIO ) +import Data.ByteString + ( ByteString ) import Data.Proxy ( Proxy (..) ) import Test.Hspec @@ -46,6 +48,7 @@ import Test.QuickCheck.Monadic import Test.Text.Roundtrip ( textRoundtrip ) +import qualified Data.ByteArray as BA spec :: Spec spec = do @@ -59,6 +62,9 @@ spec = do property prop_passphraseRoundtripFail it "checkPassphrase fails when hash is malformed" $ property prop_passphraseHashMalformed + describe "EncryptWithPBKDF2 goldens" $ do + pbkdf2Golden passphraseGolden1 + pbkdf2Golden passphraseGolden2 {------------------------------------------------------------------------------- Properties @@ -116,3 +122,54 @@ whenSupported EncryptWithScrypt | otherwise = id +pbkdf2Golden :: Golden -> Spec +pbkdf2Golden g = describe ("passphrase = " <> show (unwrap (passphrase g))) $ do + it "preparePassphrase" $ do + unwrap (preparePassphrase EncryptWithPBKDF2 (passphrase g)) + `shouldBe` unwrap (prepared g) + it "encryptPassphrase" $ do + unwrap (snd (encryptPassphrase (passphrase g) salt)) + `shouldBe` unwrap (hash g) + + it "checkPassphrase" $ do + checkPassphrase EncryptWithPBKDF2 (passphrase g) (hash g) + `shouldBe` Right () + + + where + -- Generated with 'genSalt' + salt :: Passphrase "salt" + salt = Passphrase "\x85\x80\x1b#ÓèÚ\x1b\x86>\xd8ØHëÎ" + + -- To have actual values in counterexamples, rather than just + -- : + unwrap :: BA.ByteArrayAccess i => i -> ByteString + unwrap = BA.convert @_ @ByteString + +data Golden = Golden + { passphrase :: Passphrase "user" + , prepared :: Passphrase "encryption" + , hash :: PassphraseHash + } + +passphraseGolden1 :: Golden +passphraseGolden1 = Golden + { passphrase = Passphrase "passphrase" + , prepared = Passphrase "passphrase" + , hash = PassphraseHash + "\SI\133\128\ESC#\211\232\218\ESC\134>\216\216H\235\206\206\211\134'\ + \\135\253\237\244\226\143\SYN\239!}\247\172\168\CAN\212\DC1\SI\255\235\ + \\215\241\DC4\181\133\177\232=\190\154\249\ETB\EOTd\176\149\249\216\133\ + \\141\188P\188s\159q\250\159^dj\STX\ACK$O@\208\138\236wp" + } + +passphraseGolden2 :: Golden +passphraseGolden2 = Golden + { passphrase = Passphrase "" + , prepared = Passphrase "" + , hash = PassphraseHash + "\SI\133\128\ESC#\211\232\218\ESC\134>\216\216H\235\206\130\173|\250\ + \\215\130\227^\155\216\176\NUL\145p\190P\CAN\254\155\190\140\DLE\208\ + \\194\207)X\171p*Y\170\192eiZ\243\\\222Mr\174\av\NAK\238\183\DC2\156\ + \\203\196\156,,\245X\161\242\160\148\217k\EM\234" + } From 33d535af53e75819ca4a289f28151f238c1d4e9a Mon Sep 17 00:00:00 2001 From: Rodney Lorrimar Date: Fri, 1 Apr 2022 23:27:18 +1000 Subject: [PATCH 14/15] Use more hex in passphrase tests --- .../src/Test/Integration/Plutus.hs | 9 +- .../Scenario/API/Shelley/Wallets.hs | 4 +- .../Wallet/Primitive/Passphrase/Types.hs | 4 +- lib/core/src/Cardano/Wallet/Unsafe.hs | 13 +- .../Wallet/Primitive/Passphrase/LegacySpec.hs | 148 ++++++++++-------- .../Wallet/Primitive/PassphraseSpec.hs | 26 +-- 6 files changed, 113 insertions(+), 91 deletions(-) diff --git a/lib/core-integration/src/Test/Integration/Plutus.hs b/lib/core-integration/src/Test/Integration/Plutus.hs index ceb353a7d1e..43434f456b0 100644 --- a/lib/core-integration/src/Test/Integration/Plutus.hs +++ b/lib/core-integration/src/Test/Integration/Plutus.hs @@ -38,7 +38,7 @@ import Cardano.Wallet.Api.Types import Cardano.Wallet.Primitive.Types.Hash ( Hash (..) ) import Cardano.Wallet.Unsafe - ( unsafeFromHex, unsafeRight ) + ( unsafeFromHexText, unsafeRight ) import Codec.Binary.Bech32.TH ( humanReadablePart ) import Codec.Serialise @@ -100,7 +100,7 @@ alwaysTrueValidator = hashScript :: Text -> ApiT (Hash "TokenPolicy") hashScript = - ApiT . Hash . blake2b224 . unsafeFromHex . ("01" <>) . T.encodeUtf8 + ApiT . Hash . blake2b224 . unsafeFromHexText . ("01" <>) -- -- Ping Pong @@ -365,7 +365,7 @@ currencyTx input = Aeson.object , CBOR.TBool True, CBOR.TNull ] transaction_witness_set = CBOR.TMap - [ (c_plutus_script, CBOR.TList [ CBOR.TBytes (fromHex policy) ]) + [ (c_plutus_script, CBOR.TList [CBOR.TBytes (unsafeFromHexText policy)]) , (c_plutus_data, CBOR.TList []) -- leave empty ] [c_plutus_script, c_plutus_data] = map CBOR.TInt [3,4] @@ -395,9 +395,6 @@ currencyTx input = Aeson.object toHex :: BS.ByteString -> Text toHex = T.decodeUtf8 . Base16.encode -fromHex :: Text -> BS.ByteString -fromHex = Base16.decodeLenient . T.encodeUtf8 - -- | Minting policy that mints 1_000 units of "apfel" and 1 unit of "banana" -- when a specific UTxO is spent (which can be done only once). mkCurrencyPolicy :: ApiWalletInput n -> (Text, ApiT (Hash "TokenPolicy")) diff --git a/lib/core-integration/src/Test/Integration/Scenario/API/Shelley/Wallets.hs b/lib/core-integration/src/Test/Integration/Scenario/API/Shelley/Wallets.hs index a6edc92676f..d79cd6b674c 100644 --- a/lib/core-integration/src/Test/Integration/Scenario/API/Shelley/Wallets.hs +++ b/lib/core-integration/src/Test/Integration/Scenario/API/Shelley/Wallets.hs @@ -49,7 +49,7 @@ import Cardano.Wallet.Primitive.SyncProgress import Cardano.Wallet.Primitive.Types ( walletNameMaxLength, walletNameMinLength ) import Cardano.Wallet.Unsafe - ( unsafeFromHex, unsafeXPub ) + ( unsafeFromHexText, unsafeXPub ) import Control.Monad ( forM, forM_ ) import Control.Monad.IO.Class @@ -1150,7 +1150,7 @@ spec = describe "SHELLEY_WALLETS" $ do let sigBytes = BL.toStrict $ getFromResponse id rSig let sig = CC.xsignature sigBytes let key = unsafeXPub $ fst (getFromResponse #getApiVerificationKey rKey) <> dummyChainCode - let msgHash = unsafeFromHex "1228cd0fea46f9a091172829f0c492c0516dceff67de08f585a4e048a28a6c9f" + let msgHash = unsafeFromHexText "1228cd0fea46f9a091172829f0c492c0516dceff67de08f585a4e048a28a6c9f" liftIO $ CC.verify key msgHash <$> sig `shouldBe` Right True let goldenSig = "680739414d89eb9f4377192171ce3990c7beea6132a04f327d7c954ae9e7fcfe747dd7b4b9b11acefa1aa75216b837fc81e59c24001b96356ba65598ec159d0c" :: ByteString diff --git a/lib/core/src/Cardano/Wallet/Primitive/Passphrase/Types.hs b/lib/core/src/Cardano/Wallet/Primitive/Passphrase/Types.hs index e47aa250867..7e2957e89d2 100644 --- a/lib/core/src/Cardano/Wallet/Primitive/Passphrase/Types.hs +++ b/lib/core/src/Cardano/Wallet/Primitive/Passphrase/Types.hs @@ -37,7 +37,7 @@ import Crypto.Random.Types import Data.Bifunctor ( first ) import Data.ByteArray - ( ByteArrayAccess, ScrubbedBytes ) + ( ByteArray, ByteArrayAccess, ScrubbedBytes ) import Data.ByteArray.Encoding ( Base (..), convertToBase ) import Data.Proxy @@ -169,7 +169,7 @@ instance ToText PassphraseScheme where newtype PassphraseHash = PassphraseHash { getPassphraseHash :: ScrubbedBytes } deriving stock (Show) - deriving newtype (Eq, NFData, ByteArrayAccess) + deriving newtype (Eq, Ord, Semigroup, Monoid, NFData, ByteArrayAccess, ByteArray) instance ToText PassphraseHash where toText = T.decodeUtf8 . convertToBase Base16 . getPassphraseHash diff --git a/lib/core/src/Cardano/Wallet/Unsafe.hs b/lib/core/src/Cardano/Wallet/Unsafe.hs index 9e2d60be17f..786924258fe 100644 --- a/lib/core/src/Cardano/Wallet/Unsafe.hs +++ b/lib/core/src/Cardano/Wallet/Unsafe.hs @@ -20,6 +20,7 @@ module Cardano.Wallet.Unsafe ( unsafeRight , unsafeFromHex + , unsafeFromHexText , unsafeFromBase64 , unsafeFromHexFile , unsafeDecodeAddress @@ -70,6 +71,8 @@ import Control.Monad.Trans.Except ( ExceptT (..), runExceptT ) import Data.Binary.Get ( Get, runGet ) +import Data.ByteArray + ( ByteArray ) import Data.ByteArray.Encoding ( Base (..), convertFromBase ) import Data.ByteString @@ -104,6 +107,7 @@ import qualified Data.ByteString as BS import qualified Data.ByteString.Char8 as B8 import qualified Data.ByteString.Lazy as BL import qualified Data.Text as T +import qualified Data.Text.Encoding as T import qualified Data.Text.IO as TIO -- | Take the right side of an 'Either' value. Crash badly if it was a left. @@ -111,8 +115,13 @@ unsafeRight :: (Buildable e, HasCallStack) => Either e a -> a unsafeRight = either (internalError . build) id -- | Decode an hex-encoded 'ByteString' into raw bytes, or fail. -unsafeFromHex :: HasCallStack => ByteString -> ByteString -unsafeFromHex = unsafeRight . convertFromBase @ByteString @ByteString Base16 +unsafeFromHex :: forall b. (HasCallStack, ByteArray b) => ByteString -> b +unsafeFromHex = unsafeRight . convertFromBase @ByteString @b Base16 + +-- | Decode hex-encoded 'Text' into a 'ByteString', or fail. This variant of +-- 'unsafeFromHex' may be easier to use because it's not polymorphic. +unsafeFromHexText :: HasCallStack => Text -> ByteString +unsafeFromHexText = unsafeFromHex . T.encodeUtf8 -- | Decode a base64-encoded 'ByteString' into raw bytes, or fail. unsafeFromBase64 :: HasCallStack => ByteString -> ByteString diff --git a/lib/core/test/unit/Cardano/Wallet/Primitive/Passphrase/LegacySpec.hs b/lib/core/test/unit/Cardano/Wallet/Primitive/Passphrase/LegacySpec.hs index 440df340c54..75351ebc04f 100644 --- a/lib/core/test/unit/Cardano/Wallet/Primitive/Passphrase/LegacySpec.hs +++ b/lib/core/test/unit/Cardano/Wallet/Primitive/Passphrase/LegacySpec.hs @@ -35,6 +35,10 @@ import Cardano.Wallet.Unsafe ( unsafeFromHex ) import Control.Monad.IO.Class ( liftIO ) +import Data.ByteArray + ( ByteArray ) +import Data.ByteArray.Encoding + ( Base (..), convertToBase ) import Data.ByteString ( ByteString ) import GHC.Stack @@ -48,17 +52,27 @@ import Test.QuickCheck import Test.QuickCheck.Monadic ( monadicIO ) -import qualified Data.ByteArray as BA -import qualified Data.Text.Encoding as T +spec :: Spec +spec = parallel $ do + scryptoniteSpec + onlyWithScrypt $ do + scryptPropsSpec + scryptGoldenSpec +onlyWithScrypt :: HasCallStack => Spec -> Spec +onlyWithScrypt = + if haveScrypt + then id + else before_ (pendingWith "needs to be compiled with scrypt to run") -spec :: Spec -spec = onlyWithScrypt $ describe "Scrypt tests-only cryptonite version" $ do +{------------------------------------------------------------------------------- + Cryptonite-based implementation +-------------------------------------------------------------------------------} + +scryptoniteSpec :: Spec +scryptoniteSpec = describe "Scrypt tests-only cryptonite version" $ do it "Verify passphrase" $ do let Just salt = getSalt fixturePassphraseEncrypted - let - unwrap :: PassphraseHash -> ByteString - unwrap (PassphraseHash x) = BA.convert x let x = encryptPassphraseTestingOnly (preparePassphrase fixturePassphrase) salt @@ -79,63 +93,13 @@ spec = onlyWithScrypt $ describe "Scrypt tests-only cryptonite version" $ do checkPassphraseTestingOnly (preparePassphrase fixturePassphrase) fixturePassphraseEncryptedWrongHash `shouldBe` False it "getSalt" $ do - let - unwrap :: Passphrase "salt" -> ByteString - unwrap (Passphrase x) = BA.convert x - unwrap <$> (getSalt fixturePassphraseEncrypted) - `shouldBe` Just "\250:-\180R\EM\141Y\151\160.\203\149\&0YZV3\ENQ~\ - \\DLEC\a\221\184[\177\nC\aR+" - - it "checkPassphrase p h(p) == Right () for Scrypt passwords" $ - property prop_passphraseFromScryptRoundtrip - it "p /= p' => checkPassphrase p' h(p) == Left ErrWrongPassphrase for Scrypt passwords" $ - property prop_passphraseFromScryptRoundtripFail + let hex :: Passphrase "salt" -> ByteString + hex = convertToBase Base16 . unPassphrase + hex <$> (getSalt fixturePassphraseEncrypted) + `shouldBe` Just "fa3a2db452198d5997a02ecb9530595a5633057e104307ddb85bb10a4307522b" - parallel $ describe "golden test legacy passphrase encryption" $ do - it "compare new implementation with cardano-sl - short password" $ do - let pwd = Passphrase $ BA.convert $ T.encodeUtf8 "patate" - let hash = PassphraseHash $ BA.convert $ unsafeFromHex $ mconcat - [ "31347c387c317c574342652b796362417576356c2b4258676a344a314c" - , "6343675375414c2f5653393661364e576a2b7550766655513d3d7c2f37" - , "6738486c59723174734e394f6e4e753253302b6a65515a6b5437316b45" - , "414941366a515867386539493d" - ] - checkPassphrase EncryptWithScrypt pwd hash `shouldBe` Right () - it "compare new implementation with cardano-sl - normal password" $ do - let pwd = Passphrase @"user" $ BA.convert $ T.encodeUtf8 "Secure Passphrase" - let hash = PassphraseHash $ BA.convert $ unsafeFromHex $ mconcat - [ "31347c387c317c714968506842665966555a336f5156434c384449744b" - , "677642417a6c584d62314d6d4267695433776a556f3d7c53672b436e30" - , "4232766b4475682f704265335569694577633364385845756f55737661" - , "42514e62464443353569474f4135736e453144326743346f47564c472b" - , "524331385958326c6863552f36687a38432f496172773d3d" - ] - checkPassphrase EncryptWithScrypt pwd hash `shouldBe` Right () - it "compare new implementation with cardano-sl - empty password" $ do - let pwd = Passphrase @"user" $ BA.convert $ T.encodeUtf8 "" - let hash = PassphraseHash $ BA.convert $ unsafeFromHex $ mconcat - [ "31347c387c317c5743424875746242496c6a66734d764934314a30727a7" - , "9663076657375724954796376766a793150554e377452673d3d7c54753" - , "434596d6e547957546c5759674a3164494f7974474a7842632b432f786" - , "2507657382b5135356a38303d" - ] - checkPassphrase EncryptWithScrypt pwd hash `shouldBe` Right () - it "compare new implementation with cardano-sl - cardano-wallet password" $ do - let pwd = Passphrase @"user" $ BA.convert $ T.encodeUtf8 "cardano-wallet" - let hash = PassphraseHash $ BA.convert $ unsafeFromHex $ mconcat - [ "31347c387c317c2b6a6f747446495a6a566d586f43374c6c54425a576c" - , "597a425834515177666475467578436b4d485569733d7c78324d646738" - , "49554a3232507235676531393575445a76583646552b7757395a6a6a2f" - , "51303054356c654751794279732f7662753367526d726c316c657a7150" - , "43676d364e6758476d4d2f4b6438343265304b4945773d3d" - ] - checkPassphrase EncryptWithScrypt pwd hash `shouldBe` Right () - -onlyWithScrypt :: HasCallStack => Spec -> Spec -onlyWithScrypt = - if haveScrypt - then id - else before_ (pendingWith "needs to be compiled with scrypt to run") +unwrap :: ByteArray b => b -> ByteString +unwrap = convertToBase Base16 -- | Default passphrase used for fixture wallets fixturePassphrase :: Passphrase "user" @@ -146,7 +110,7 @@ fixturePassphraseWrong = Passphrase "wrong" -- | fixturePassphrase encrypted by Scrypt function fixturePassphraseEncrypted :: PassphraseHash -fixturePassphraseEncrypted = PassphraseHash $ BA.convert $ unsafeFromHex +fixturePassphraseEncrypted = unsafeFromHex "31347c387c317c2b6a6f747446495a6a566d586f43374c6c54425a576c\ \597a425834515177666475467578436b4d485569733d7c78324d646738\ \49554a3232507235676531393575445a76583646552b7757395a6a6a2f\ @@ -154,7 +118,7 @@ fixturePassphraseEncrypted = PassphraseHash $ BA.convert $ unsafeFromHex \43676d364e6758476d4d2f4b6438343265304b4945773d3d" fixturePassphraseEncryptedWrongSalt :: PassphraseHash -fixturePassphraseEncryptedWrongSalt = PassphraseHash $ BA.convert $ unsafeFromHex +fixturePassphraseEncryptedWrongSalt = unsafeFromHex "31347c387c317c206a6f747446495a6a566d586f43374c6c54425a576c\ \597a425834515177666475467578436b4d485569733d7c78324d646738\ \49554a3232507235676531393575445a76583646552b7757395a6a6a2f\ @@ -162,17 +126,69 @@ fixturePassphraseEncryptedWrongSalt = PassphraseHash $ BA.convert $ unsafeFromHe \43676d364e6758476d4d2f4b6438343265304b4945773d3d" fixturePassphraseEncryptedWrongHash :: PassphraseHash -fixturePassphraseEncryptedWrongHash = PassphraseHash $ BA.convert $ unsafeFromHex +fixturePassphraseEncryptedWrongHash = unsafeFromHex "31347c387c317c2b6a6f747446495a6a566d586f43374c6c54425a576c\ \597a425834515177666475467578436b4d485569733d7c78324d646738\ \49554a3232507235676531393575445a76583646552b7757395a6a6a2f\ \51303054356c654751794279732f7662753367526d726c316c657a7150\ \43676d364e6758476d4d2f4b6438343265304b4945773d30" +{------------------------------------------------------------------------------- + Golden Tests +-------------------------------------------------------------------------------} + +scryptGoldenSpec :: Spec +scryptGoldenSpec = + describe "golden tests comparing this implementation with cardano-sl" $ do + it "short password" $ do + let pwd = Passphrase "patate" + let hash = unsafeFromHex + "31347c387c317c574342652b796362417576356c2b4258676a344a314c\ + \6343675375414c2f5653393661364e576a2b7550766655513d3d7c2f37\ + \6738486c59723174734e394f6e4e753253302b6a65515a6b5437316b45\ + \414941366a515867386539493d" + checkPassphrase EncryptWithScrypt pwd hash `shouldBe` Right () + + it "normal password" $ do + let pwd = Passphrase @"user" "Secure Passphrase" + let hash = unsafeFromHex + "31347c387c317c714968506842665966555a336f5156434c384449744b\ + \677642417a6c584d62314d6d4267695433776a556f3d7c53672b436e30\ + \4232766b4475682f704265335569694577633364385845756f55737661\ + \42514e62464443353569474f4135736e453144326743346f47564c472b\ + \524331385958326c6863552f36687a38432f496172773d3d" + checkPassphrase EncryptWithScrypt pwd hash `shouldBe` Right () + + it "empty password" $ do + let pwd = Passphrase @"user" "" + let hash = unsafeFromHex + "31347c387c317c5743424875746242496c6a66734d764934314a30727a\ + \79663076657375724954796376766a793150554e377452673d3d7c5475\ + \3434596d6e547957546c5759674a3164494f7974474a7842632b432f78\ + \62507657382b5135356a38303d" + checkPassphrase EncryptWithScrypt pwd hash `shouldBe` Right () + + it "cardano-wallet password" $ do + let pwd = Passphrase @"user" "cardano-wallet" + let hash = unsafeFromHex + "31347c387c317c2b6a6f747446495a6a566d586f43374c6c54425a576c\ + \597a425834515177666475467578436b4d485569733d7c78324d646738\ + \49554a3232507235676531393575445a76583646552b7757395a6a6a2f\ + \51303054356c654751794279732f7662753367526d726c316c657a7150\ + \43676d364e6758476d4d2f4b6438343265304b4945773d3d" + checkPassphrase EncryptWithScrypt pwd hash `shouldBe` Right () + {------------------------------------------------------------------------------- Properties -------------------------------------------------------------------------------} +scryptPropsSpec :: Spec +scryptPropsSpec = describe "Legacy scrypt password encryption" $ do + it "checkPassphrase p h(p) == Right ()" $ + property prop_passphraseFromScryptRoundtrip + it "p /= p' => checkPassphrase p' h(p) == Left ErrWrongPassphrase" $ + property prop_passphraseFromScryptRoundtripFail + prop_passphraseFromScryptRoundtrip :: Passphrase "user" -> Property diff --git a/lib/core/test/unit/Cardano/Wallet/Primitive/PassphraseSpec.hs b/lib/core/test/unit/Cardano/Wallet/Primitive/PassphraseSpec.hs index 476f80cb543..4556ded483d 100644 --- a/lib/core/test/unit/Cardano/Wallet/Primitive/PassphraseSpec.hs +++ b/lib/core/test/unit/Cardano/Wallet/Primitive/PassphraseSpec.hs @@ -31,6 +31,8 @@ import Cardano.Wallet.Primitive.Passphrase.Gen ) import Cardano.Wallet.Primitive.Passphrase.Legacy ( haveScrypt ) +import Cardano.Wallet.Unsafe + ( unsafeFromHex ) import Control.Monad.IO.Class ( liftIO ) import Data.ByteString @@ -51,11 +53,11 @@ import Test.Text.Roundtrip import qualified Data.ByteArray as BA spec :: Spec -spec = do - parallel $ describe "Text Roundtrip" $ do +spec = parallel $ do + describe "Text Roundtrip" $ do textRoundtrip $ Proxy @(Passphrase "user") - parallel $ describe "Passphrases" $ do + describe "Passphrases" $ do it "checkPassphrase p h(p) == Right ()" $ property prop_passphraseRoundtrip it "p /= p' => checkPassphrase p' h(p) == Left ErrWrongPassphrase" $ @@ -156,20 +158,18 @@ passphraseGolden1 :: Golden passphraseGolden1 = Golden { passphrase = Passphrase "passphrase" , prepared = Passphrase "passphrase" - , hash = PassphraseHash - "\SI\133\128\ESC#\211\232\218\ESC\134>\216\216H\235\206\206\211\134'\ - \\135\253\237\244\226\143\SYN\239!}\247\172\168\CAN\212\DC1\SI\255\235\ - \\215\241\DC4\181\133\177\232=\190\154\249\ETB\EOTd\176\149\249\216\133\ - \\141\188P\188s\159q\250\159^dj\STX\ACK$O@\208\138\236wp" + , hash = unsafeFromHex + "0f85801b23d3e8da1b863ed8d848ebceced3862787fdedf4e28f16ef217df7ac\ + \a818d4110fffebd7f114b585b1e83dbe9af9170464b095f9d8858dbc50bc739f\ + \71fa9f5e646a0206244f40d08aec7770" } passphraseGolden2 :: Golden passphraseGolden2 = Golden { passphrase = Passphrase "" , prepared = Passphrase "" - , hash = PassphraseHash - "\SI\133\128\ESC#\211\232\218\ESC\134>\216\216H\235\206\130\173|\250\ - \\215\130\227^\155\216\176\NUL\145p\190P\CAN\254\155\190\140\DLE\208\ - \\194\207)X\171p*Y\170\192eiZ\243\\\222Mr\174\av\NAK\238\183\DC2\156\ - \\203\196\156,,\245X\161\242\160\148\217k\EM\234" + , hash = unsafeFromHex + "0f85801b23d3e8da1b863ed8d848ebce82ad7cfad782e35e9bd8b0009170be50\ + \18fe9bbe8c10d0c2cf2958ab702a59aac065695af35cde4d72ae077615eeb712\ + \9ccbc49c2c2cf558a1f2a094d96b19ea" } From 72d44e96dc63341a43cb913711383932acc4f8bc Mon Sep 17 00:00:00 2001 From: IOHK Date: Fri, 21 Jan 2022 14:24:45 +0000 Subject: [PATCH 15/15] Regenerate nix --- .../stack-nix/cardano-wallet-core-integration.nix | 1 - nix/materialized/stack-nix/cardano-wallet-core.nix | 12 +++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/nix/materialized/stack-nix/cardano-wallet-core-integration.nix b/nix/materialized/stack-nix/cardano-wallet-core-integration.nix index aadc4dd1ee4..5b470be2d0a 100644 --- a/nix/materialized/stack-nix/cardano-wallet-core-integration.nix +++ b/nix/materialized/stack-nix/cardano-wallet-core-integration.nix @@ -85,7 +85,6 @@ (hsPkgs."resourcet" or (errorHandler.buildDepError "resourcet")) (hsPkgs."retry" or (errorHandler.buildDepError "retry")) (hsPkgs."say" or (errorHandler.buildDepError "say")) - (hsPkgs."scrypt" or (errorHandler.buildDepError "scrypt")) (hsPkgs."serialise" or (errorHandler.buildDepError "serialise")) (hsPkgs."string-interpolate" or (errorHandler.buildDepError "string-interpolate")) (hsPkgs."template-haskell" or (errorHandler.buildDepError "template-haskell")) diff --git a/nix/materialized/stack-nix/cardano-wallet-core.nix b/nix/materialized/stack-nix/cardano-wallet-core.nix index b8e6f602b16..f4426b7277a 100644 --- a/nix/materialized/stack-nix/cardano-wallet-core.nix +++ b/nix/materialized/stack-nix/cardano-wallet-core.nix @@ -8,7 +8,7 @@ , config , ... }: { - flags = { release = false; }; + flags = { release = false; scrypt = true; }; package = { specVersion = "1.10"; identifier = { name = "cardano-wallet-core"; version = "2022.1.18"; }; @@ -114,7 +114,6 @@ (hsPkgs."retry" or (errorHandler.buildDepError "retry")) (hsPkgs."safe" or (errorHandler.buildDepError "safe")) (hsPkgs."scientific" or (errorHandler.buildDepError "scientific")) - (hsPkgs."scrypt" or (errorHandler.buildDepError "scrypt")) (hsPkgs."servant" or (errorHandler.buildDepError "servant")) (hsPkgs."servant-client" or (errorHandler.buildDepError "servant-client")) (hsPkgs."servant-server" or (errorHandler.buildDepError "servant-server")) @@ -148,7 +147,7 @@ (hsPkgs."Win32-network" or (errorHandler.buildDepError "Win32-network")) (hsPkgs."QuickCheck" or (errorHandler.buildDepError "QuickCheck")) (hsPkgs."cardano-wallet-test-utils" or (errorHandler.buildDepError "cardano-wallet-test-utils")) - ]; + ] ++ (pkgs.lib).optional (flags.scrypt) (hsPkgs."scrypt" or (errorHandler.buildDepError "scrypt")); buildable = true; modules = [ "Paths_cardano_wallet_core" @@ -217,6 +216,11 @@ "Cardano/Wallet/Primitive/Model" "Cardano/Wallet/Primitive/Slotting" "Cardano/Wallet/Primitive/SyncProgress" + "Cardano/Wallet/Primitive/Passphrase" + "Cardano/Wallet/Primitive/Passphrase/Current" + "Cardano/Wallet/Primitive/Passphrase/Gen" + "Cardano/Wallet/Primitive/Passphrase/Legacy" + "Cardano/Wallet/Primitive/Passphrase/Types" "Cardano/Wallet/Primitive/Types" "Cardano/Wallet/Primitive/Types/Address" "Cardano/Wallet/Primitive/Types/Coin" @@ -424,6 +428,8 @@ "Cardano/Wallet/Primitive/Migration/PlanningSpec" "Cardano/Wallet/Primitive/Migration/SelectionSpec" "Cardano/Wallet/Primitive/ModelSpec" + "Cardano/Wallet/Primitive/PassphraseSpec" + "Cardano/Wallet/Primitive/Passphrase/LegacySpec" "Cardano/Wallet/Primitive/Slotting/Legacy" "Cardano/Wallet/Primitive/SlottingSpec" "Cardano/Wallet/Primitive/SyncProgressSpec"