Skip to content

Conversation

TheBlueMatt
Copy link
Contributor

This commit adds support for ckeys, or enCrypted private keys, to the wallet.

See commit message for a more detailed description.

@TheBlueMatt
Copy link
Contributor Author

@TheBlueMatt
Copy link
Contributor Author

Changed topupkeypool to keypoolrefill at tcatm's suggestion

@TheBlueMatt
Copy link
Contributor Author

A note for users of the previous version - Ill write a converter tomorrow that will try to convert the old encrypted wallets to this new format.

@TheBlueMatt
Copy link
Contributor Author

TODO List:
Anything marked with //TODO
s/password/passphrase/
write better text for first encryption ie "DONT LOSE THIS PASSWORD" and such
An option to decrypt the wallet? (Decided against this one as noone seemed to care if it got implemented, if others here disagree, please respond)
Figure out why walletphassphrase is lagging (top up keypool?)

@gavinandresen
Copy link
Contributor

Looks OK, I like the feature set, I haven't had a chance to compile and test it.

@jgarzik
Copy link
Contributor

jgarzik commented Jun 28, 2011

Looks OK at first glance. Will give it an in-depth look and test RSN.

One nit: mlock() can definitely fail based variable conditions such as ulimit. The code never checks for mlock failure, and seems to assume you may munlock even if mlock failed.

@TheBlueMatt
Copy link
Contributor Author

If it fails, it should continue, yes we are potentially not as secure, but we should stop just because we cant mlock(). Also, since munlock() is called implicitly when we deallocate, its unnecessary, I pulled it out.
Oh got Im stupid, sorry, readded that.

@gmaxwell
Copy link
Contributor

I spent a couple days beating the hell out of this implementation and Matt has quickly fixed all the bugs I've encountered.

In particular I've now sent thousands of transactions and thousands of getaddresses in an abusive testing rig which is constantly unlocking/locking/password changing/account moving, etc. and not lost any coin. I've basically run out of obvious automated testing at this point.

I am, however, unable to do GUI testing, so I hope and assume other people are beating on that.

To the best of my ability to determine this is now ready for mainline and testing by a broader audience.

I think that before release the wallettools probably need to be updated to support this: both as an additional validation of the implementation, but also so that there isn't a gap in recovery tools, but I don't see any reason that should block mainlining it.

@joric
Copy link

joric commented Jul 4, 2011

Just tried to build 499334e on win32 using mingw, works fine, except a couple of things.

  1. In crypter.cpp you have to move headers.h to the very top otherwise you'll get a whole bunch of "Cannot convert from 'const wchar_t *' to '_TCHAR *'" (more precisely, wx.h should be included before windows.h).
  2. Cancel button in the encryption dialog does not work as expected (buggy wxGetPasswordFromUser).
    P.S. Also note encryption can't be undone yet, so don't try it on your wallets. )

@joric
Copy link

joric commented Jul 4, 2011

Just figured out if you're trying to read encrypted wallet with official client (0.3.24rc1 in my case) wallet gets destroyed and not readable by anything. It's not very nice.

@TheBlueMatt
Copy link
Contributor Author

If you try to read it with the old client, the client won't (shouldn't) touch it and just complain that it is "Unable to read wallet.dat". However, if you reopen it with this client, it should open fine.
Also, thanks for the mingw heads-up, I suppose I must've changed the header order at some point after I tested on mingw...
Oh well, should be fixed now.

@joric
Copy link

joric commented Jul 4, 2011

Well, not really. Official 0.3.23 says basically the same as 0.3.24rc1: EXCEPTION: St13runtime_error ReserveKeyFromKeyPool() : unknown key in key pool (...)bitcoin.exe in AppInit(), EXCEPTION: St13runtime_error ReserveKeyFromKeyPool() : unknown key in key pool (...)bitcoin.exe in CMyApp::OnUnhandledException(), Runtime error!

After that wallet.dat becomes completely inaccesible, 499334e says "Error loading wallet.dat". If someone is interested I've tried that only on a new wallet (with no transactions).

@TheBlueMatt
Copy link
Contributor Author

Ah, shit you are totally right, and here I was thinking the client was smarter than that...Ill make sure a fix goes into 0.3.24 so at least people can downgrade to that safely.
Oh, forgot to mention, I changed master key format last night, so wallet encrypted with this previously will not load.

@TheBlueMatt
Copy link
Contributor Author

See #378

@graingert
Copy link
Contributor

would it be possible to encrypt the wallet with a null/default pass-phrase by default rather than leaving the wallet unencrypted?

@TheBlueMatt
Copy link
Contributor Author

null/default passphrase is 100% identical to no encryption, if an attacker gets the wallet, they can still just dump it in their .bitcoin folder and send coins, its better to be backward compatible if users dont encrypt than just break that too for nothing.

sipa and others added 2 commits July 8, 2011 15:46
Inline comment and idea come from the encprivkeys branch
by Matt Corallo <matt@bluematt.me>.
@sipa
Copy link
Member

sipa commented Jul 9, 2011

I am not sure the mlock() wrappers are correct. ((void *)(((size_t)(a)) & ((size_t)(((PAGESIZE)<<1)-1)))) seems to throw away the high bits of the address, instead of the low ones.

I suggest using:

mlock((void*)((size_t)a & ~(PAGESIZE - 1)), ((((size_t)a + b - 1) | (PAGESIZE - 1)) + 1) - ((size_t)a & ~(PAGESIZE - 1)));

Here is a test program:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

#define PAGESIZE 4096

int my_mlock(void *a, size_t b)
{
    void *start = (void*)((size_t)a & ~(PAGESIZE - 1));
    void *stop  = (void*)((((size_t)a + b - 1) | (PAGESIZE - 1)) + 1);
    size_t range = stop - start;
    assert(start <= a);
    assert(start+range >= (a+b));
    assert((((size_t)start) & (PAGESIZE-1)) == 0);
    assert((((size_t)start+range) & (PAGESIZE-1)) == 0);
    // return mlock(start, range);
    return 0;
}

int main(void)
{
    size_t as[] = {0x400000, 0x3FFFFF, 0x3FFF00, 0x400100};
    size_t bs[] = {0,1,2,3,4,7,15,127,128,129,255,256,257,4000,4095,4096,4097,4100,4200,8000,8190,8300};
    for (int na=0; na<sizeof(as)/sizeof(as[0]); na++) {
      for (int nb=0; nb<sizeof(bs)/sizeof(bs[0]); nb++) {
        my_mlock((void*)(as[na]),bs[nb]);
      }
    }
    return 0;
}

@sipa
Copy link
Member

sipa commented Jul 9, 2011

Apart from the above comment: ACK.

@TheBlueMatt
Copy link
Contributor Author

re gavin's comment
"This needs a comment explaining what it is for-- "This class writes an addrIncoming entry that causes pre-0.4 clients to crash on startup if reading a private-key-encrypted wallet.""
done.

Matt Corallo and others added 8 commits July 13, 2011 02:11
This commit adds support for ckeys, or enCrypted private keys, to the wallet.
All keys are stored in memory in their encrypted form and thus the passphrase
is required from the user to spend coins, or to create new addresses.

Keys are encrypted with AES-256-CBC using OpenSSL's EVP library. The key is
calculated via EVP_BytesToKey using SHA512 with (by default) 25000 rounds and
a random salt.

By default, the user's wallet remains unencrypted until they call the RPC
command encryptwallet <passphrase> or, from the GUI menu, Options->
Encrypt Wallet.

When the user is attempting to call RPC functions which require the password
to unlock the wallet, an error will be returned unless they call
walletpassphrase <passphrase> <time to keep key in memory> first.

A keypoolrefill command has been added which tops up the users keypool
(requiring the passphrase via walletpassphrase first).
keypoolsize has been added to the output of getinfo to show the user the
number of keys left before they need to specify their passphrase (and call
keypoolrefill).

Note that walletpassphrase will automatically fill keypool in a separate
thread which it spawns when the passphrase is set. This could cause some
delays in other threads waiting for locks on the wallet passphrase, including
one which could cause the passphrase to be stored longer than expected,
however it will not allow the passphrase to be used longer than expected as
ThreadCleanWalletPassphrase will attempt to get a lock on the key as soon
as the specified lock time has arrived.

When the keypool runs out (and wallet is locked) GetOrReuseKeyFromPool
returns vchDefaultKey, meaning miners may start to generate many blocks to
vchDefaultKey instead of a new key each time.

A walletpassphrasechange <oldpassphrase> <newpassphrase> has been added to
allow the user to change their password via RPC.

Whenever keying material (unencrypted private keys, the user's passphrase,
the wallet's AES key) is stored unencrypted in memory, any reasonable attempt
is made to mlock/VirtualLock that memory before storing the keying material.
This is not true in several (commented) cases where mlock/VirtualLocking the
memory is not possible.

Although encryption of private keys in memory can be very useful on desktop
systems (as some small amount of protection against stupid viruses), on an
RPC server, the password is entered fairly insecurely. Thus, the only main
advantage encryption has for RPC servers is for RPC servers that do not spend
coins, except in rare cases, eg. a webserver of a merchant which only receives
payment except for cases of manual intervention.

Thanks to jgarzik for the original patch and sipa, gmaxwell and many others
for all their input.

Conflicts:

	src/wallet.cpp
This speeds up the encryption process significantly.
This prevents old clients from opening, and thus corrupting
or otherwise causing harm to encrypted wallets.
@gavinandresen
Copy link
Contributor

ACK, looks ready-for-prime-time to me.

jgarzik pushed a commit that referenced this pull request Jul 13, 2011
Wallet Private Key Encryption (on CWallet)
@jgarzik jgarzik merged commit 0bad8e4 into bitcoin:master Jul 13, 2011
@groffer
Copy link

groffer commented Jul 16, 2011

Unit tests?

ptschip pushed a commit to ptschip/bitcoin that referenced this pull request Mar 11, 2017
Handle a special case for  Parallel Validation
classesjack pushed a commit to classesjack/bitcoin that referenced this pull request Jan 2, 2018
@NickyYangYang
Copy link

Why the unencrypted master key is stored in memory? Why not unencrypted private key?

fjahr pushed a commit to fjahr/bitcoin that referenced this pull request Jul 24, 2019
c8fbc3c [ECDH API change] Allow pass arbitrary data to hash function (Kirill Fomichev)
b00be65 [ECDH API change] Support custom hash function (Kirill Fomichev)

Pull request description:

  Solve bitcoin#352

Tree-SHA512: f5985874d03e976cdb3d59036af7720636ad1488da40fd3bd7881b1fb71b05036a952013d519baa84c4ce4b558bdef25c4ce76b384b297e4d0aece9e37e78a01
0xartem pushed a commit to Crowndev/crown-core that referenced this pull request Feb 29, 2020
0xartem pushed a commit to Crowndev/crown-core that referenced this pull request Feb 29, 2020
@bitcoin bitcoin locked as resolved and limited conversation to collaborators Sep 8, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.