diff --git a/doc/security-warnings.md b/doc/security-warnings.md index c5c340854..2e40e92fe 100644 --- a/doc/security-warnings.md +++ b/doc/security-warnings.md @@ -14,6 +14,34 @@ make proving keys generated on 64-bit systems unusable on 32-bit and big-endian systems. It's unclear if a warning will be issued in this case, or if the proving system will be silently compromised. +Wallet Encryption +----------------- + +Wallet encryption is disabled, for several reasons: + +- Encrypted wallets are unable to correctly detect shielded spends (due to the + nature of unlinkability of JoinSplits) and can incorrectly show larger + available shielded balances until the next time the wallet is unlocked. This + problem was not limited to failing to recognize the spend; it was possible for + the shown balance to increase by the amount of change from a spend, without + deducting the spent amount. + +- While encrypted wallets prevent spending of funds, they do not maintain the + shielding properties of JoinSplits (due to the need to detect spends). That + is, someone with access to an encrypted wallet.dat has full visibility of + your entire transaction graph (other than newly-detected spends, which suffer + from the earlier issue). + +- We were concerned about the resistance of the algorithm used to derive wallet + encryption keys (inherited from Bitcoin) to dictionary attacks by a powerful + attacker. If and when we re-enable wallet encryption, it is likely to be with + a modern passphrase-based key derivation algorithm designed for greater + resistance to dictionary attack, such as Argon2i. + +You should use full-disk encryption (or encryption of your home directory) to +protect your wallet at rest, and should assume (even unprivileged) users who are +runnng on your OS can read your wallet.dat file. + Side-Channel Attacks -------------------- diff --git a/qa/rpc-tests/keypool.py b/qa/rpc-tests/keypool.py index aee29a596..1f8979692 100755 --- a/qa/rpc-tests/keypool.py +++ b/qa/rpc-tests/keypool.py @@ -98,7 +98,7 @@ def main(): os.makedirs(options.tmpdir) initialize_chain(options.tmpdir) - nodes = start_nodes(1, options.tmpdir) + nodes = start_nodes(1, options.tmpdir, extra_args=[['-developerencryptwallet']]) run_test(nodes, options.tmpdir) diff --git a/qa/rpc-tests/wallet_nullifiers.py b/qa/rpc-tests/wallet_nullifiers.py index 7efd70f6a..6d7449138 100755 --- a/qa/rpc-tests/wallet_nullifiers.py +++ b/qa/rpc-tests/wallet_nullifiers.py @@ -10,6 +10,10 @@ from time import * class WalletNullifiersTest (BitcoinTestFramework): + def setup_nodes(self): + return start_nodes(4, self.options.tmpdir, + extra_args=[['-developerencryptwallet']] * 4) + def run_test (self): # add zaddr to node 0 myzaddr0 = self.nodes[0].z_getnewaddress() diff --git a/src/test/rpc_wallet_tests.cpp b/src/test/rpc_wallet_tests.cpp index 68883f2f8..2f674df0f 100644 --- a/src/test/rpc_wallet_tests.cpp +++ b/src/test/rpc_wallet_tests.cpp @@ -1090,6 +1090,9 @@ BOOST_AUTO_TEST_CASE(rpc_wallet_encrypted_wallet_zkeys) Array arr = retValue.get_array(); BOOST_CHECK(arr.size() == n); + // Verify that the wallet encryption RPC is disabled + BOOST_CHECK_THROW(CallRPC("encryptwallet passphrase"), runtime_error); + // Encrypt the wallet (we can't call RPC encryptwallet as that shuts down node) SecureString strWalletPass; strWalletPass.reserve(100); diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 4d6608570..e31619475 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -1979,10 +1979,18 @@ Value encryptwallet(const Array& params, bool fHelp) { if (!EnsureWalletIsAvailable(fHelp)) return Value::null; - + + auto fEnableWalletEncryption = GetBoolArg("-developerencryptwallet", false); + + std::string strWalletEncryptionDisabledMsg = ""; + if (!fEnableWalletEncryption) { + strWalletEncryptionDisabledMsg = "\nWARNING: Wallet encryption is DISABLED. This call always fails.\n"; + } + if (!pwalletMain->IsCrypted() && (fHelp || params.size() != 1)) throw runtime_error( "encryptwallet \"passphrase\"\n" + + strWalletEncryptionDisabledMsg + "\nEncrypts the wallet with 'passphrase'. This is for first time encryption.\n" "After this, any calls that interact with private keys such as sending or signing \n" "will require the passphrase to be set prior the making these calls.\n" @@ -2008,6 +2016,9 @@ Value encryptwallet(const Array& params, bool fHelp) if (fHelp) return true; + if (!fEnableWalletEncryption) { + throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, "Error: wallet encryption is disabled."); + } if (pwalletMain->IsCrypted()) throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an encrypted wallet, but encryptwallet was called.");