forked from bitcoin/bitcoin
-
Notifications
You must be signed in to change notification settings - Fork 6
/
persistent-seed-notes.txt
184 lines (138 loc) · 5.76 KB
/
persistent-seed-notes.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
PRNG persistent seed
https://bitcointalk.org/index.php?topic=113496.msg1227163#msg1227163
Want to make the OpenSSL ECC calls safe, which internally use RAND_Bytes or
something. All our GetRand* stuff isn't security critical enough to worry about
PRNG failures.
Safe to say everywhere you see AddSeed outside of initialization is where we
should be using persistent seeds. Two uses in CWallet::EncryptWallet, the other
use in CWallet::GenerateNewKey
use either dedicated seed file or private keys
dedicated seed file really should be as secure as your wallet, IE encrypted, so
private keys would be easiest
how does RAND_add work? hashing, see openssl src: /crypto/rand/md_rand.c kinda
slow, so don't add more than we have real entropy for
latter option:
On encrypting wallet, put entropy from keys into pool.
On generating keys, same.
CPubKey CWallet::GenerateNewKey()
{
bool fCompressed = CanSupportFeature(FEATURE_COMPRPUBKEY); // default to compressed public keys if we want 0.6.0 wallets
RandAddSeedPerfmon();
CKey key;
# this happens first, before we check if the keystore is encrypted or not
key.MakeNewKey(fCompressed);
// Compressed public keys were introduced in version 0.6.0
if (fCompressed)
SetMinVersion(FEATURE_COMPRPUBKEY);
# now we try to add, some possibilities...
if (!AddKey(key))
throw std::runtime_error("CWallet::GenerateNewKey() : AddKey failed");
return key.GetPubKey();
}
bool CWallet::AddKey(const CKey& key)
{
if (!CCryptoKeyStore::AddKey(key))
return false;
if (!fFileBacked)
return true;
if (!IsCrypted())
return CWalletDB(strWalletFile).WriteKey(key.GetPubKey(), key.GetPrivKey());
return true;
}
Seems that the key is first added to the memory store, potentially encrypted,
then added to the on-disk wallet.
bool CCryptoKeyStore::AddKey(const CKey& key)
{
{
LOCK(cs_KeyStore);
if (!IsCrypted())
return CBasicKeyStore::AddKey(key);
if (IsLocked())
return false;
std::vector<unsigned char> vchCryptedSecret;
CPubKey vchPubKey = key.GetPubKey();
bool fCompressed;
if (!EncryptSecret(vMasterKey, key.GetSecret(fCompressed), vchPubKey.GetHash(), vchCryptedSecret))
return false;
if (!AddCryptedKey(key.GetPubKey(), vchCryptedSecret))
return false;
}
return true;
}
bool CBasicKeyStore::AddKey(const CKey& key)
{
bool fCompressed = false;
CSecret secret = key.GetSecret(fCompressed);
{
LOCK(cs_KeyStore);
mapKeys[key.GetPubKey().GetID()] = make_pair(secret, fCompressed);
}
return true;
}
So we have a possible problem, the keystore is protected with an ECC keypair,
which means you can add new keys to it without having the wallet unlocked.
Easy case:
if (IsLocked()){
// add secret key data, best
} (!IsLocked()){
// add the encrypted secret key data, good unless attacker has disk access
}
CWallet doesn't have access to the data itself, so we should probably add to
CKeyStore:
void CKeyStore::AddPrivateKeysToRandPool() : virtual
Keys appear to always be stored in memory, no disk database lookup required.
void BasicKeyStore::AddPrivateKeysToRandPool(){
// mapKeys is a CKeyID:(CSecret,bool) map, can iterate over all values in the map, adding secrets as we go
}
void CCryptoKeyStore::AddPrivateKeysToRandPool()
{
// Only do this once
if (KeysAddedAlready) return;
// The following has direct openssl calls, which really should be a
// separate util.h EntropyGather class that initializes some *secure* memory,
// has an Update() method which XOR's in additional data, and a AddToPool()
// method which takes that XOR'd buffer and calls RAND_add()
//
// We can initialize the XOR buffer with a *fixed* number of bytes, and
// then loop around it. Assuming that the entropy is *consecutive* we'll
// add as much of it as the buffer length. Consider "____RANDOM___" ->
// three byte buffer -> N^M,R^D,A^O assuming the _ characters do nothing
//
// If entropy isn't consecutive, we can end up XORing bits together. Nasty
// trap, but OK for adding private keys.
//
//
// Maybe better to skip the XOR stuff, and just use
// SHA256_Init,Updated,Final even if it's not absolutely ideologically
// pure. After all, it's highly unlikely for SHA256 to break, because any
// remotely-exploitable change implies that output changes, which should be
// noticed *very* quickly.
//
//
// Q: can secure_allocator allocate a byte array compatible with OpenSSL RAND_add?
// Q: as above, but for SHA256?
// Q: is this SHA256 routine used for everything? (so breakage will be noticed)
{
LOCK(cs_KeyStore);
if (!IsCrypted()){
CBasicKeyStore::AddKeyEntropyToRandPool()
}
// mapCryptedKeys maps CKeyId:(CPubKey,ciphertext)
// allocate buffer to xor in data
secure_allocator something something
// whatever needs to be done to turn this into a loop
CryptedKeyMap::const_iterator mi = mapCryptedKeys.find(address);
if (mi != mapCryptedKeys.end())
const CPubKey &vchPubKey = (*mi).second.first;
const std::vector<unsigned char> &vchCryptedSecret = (*mi).second.second;
CSecret vchSecret;
// Attempt to decrypt the secret
DecryptSecret(vMasterKey, vchCryptedSecret, vchPubKey.GetHash(), vchSecret);
// Regardless if the attempt works or not, xor in both the
// CryptedSecret and decrypted secret
}
// Add the resulting XOR stuff in one go, assuming that we added entropy
// equivalent to one unguessable key length.
RAND_add(&vchCryptedSecret.cstr,length,entropy=32)
}
}