A couple of users have requested that we support wallets that encrypt everything, even if the wallet is watch-only, in order to have better privacy if the wallet is stolen. This PR introduces an EncryptedDatabase backend for the wallet which encrypts/decrypts each key-value record individually before reading from or writing to the database.
EncryptedDatabase is only supported for SQLite databases and descriptor wallets. This was done in order to have an easier way to get downgrade protection that also does not involve writing an existing record in plaintext (e.g. minversion or flags). SQLite has a fixed field “application id” that we already use for cross-network protection. This is reused to detect if a sqlite database is an encrypted wallet, and thus it prevents older software from attempting to open such wallets.
In order to read records from the database, EncryptedDatabase will cache the decrypted key in memory so that it can lookup the encrypted key in the database. Values will always be decrypted when read.
The encryption scheme is the same one that we use for encrypting private keys. It’s not that great, but I didn’t feel like re-implementing it, and it seems good enough. The encryption key itself is encrypted with the passphrase and stored in a new record.
Wallets with encrypted databases cannot be loaded on start. They can only be loaded by explicit user action through loadwallet which now has a db_passphrase parameter to allow the decryption of these wallets. createwallet also has a db_passphrase to create these wallets. For now, there is no way to encrypt the database after it has been created, nor is there a way to change the passphrase.