diff options
| author | Bitflash <btf01@proton.me> | 2026-08-04 10:01:36 -0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-08-04 10:01:36 -0300 |
| commit | 1db5f372c36eb701602a70a358de2344726ed734 (patch) | |
| tree | 9d028ed3097ac51368ca312ff439664c76f439b7 /src/main.cpp | |
| parent | 9176d5b3f32b907e4bafde5152828b2ae04ae8a1 (diff) | |
wallet encryption: the stack from #130–#133, with the two blockers fixed (#134)
* wallet: refuse future encrypted wallet formats
* wallet: add encrypted key unlock core
* wallet: add encryptwallet rewrite command
* wallet: read passphrases from stdin or files
* wallet: keep the derivation across encryption, and take the logs with it
Two things stopped the encryption stack from being safe to merge. Both
were found against a wallet holding real coin, not in the self-tests.
1. Encrypting turned a BIP44 wallet into a legacy one.
LoadWallet() ends by resetting the schema and the derivation counters
when no seed is in memory. That was written when "no seed after loading"
could only mean the wallet had none. An encrypted wallet has a seed --
it just has not been decrypted yet -- so the reset threw away metadata
that had been read correctly a moment earlier. Unlocking later restores
the seed and nothing restores the rest.
Measured on the wallet from the #124 proof: after encrypting, index 1
moved off BAtgSgra2X8Dqo5PJzPCLJhPwrrC2thYVJ, the address actually
holding 1.00 BTF, and the audit went from "1.00 covered by the phrase"
to "0.00 covered, 1.00 wallet.dat-only". Encrypt your wallet and the
program told you your recovery phrase no longer covered your money.
Now the condition also accepts a wallet whose seed is present but
encrypted. Same wallet, before and after: schema bip44, same four
derived addresses, 1.00 BTF still reported as covered.
2. The plaintext keys stayed behind in the Berkeley DB logs.
wallet.dat comes out clean -- verified by scanning the raw file for the
118 bytes of a known private key. database/log.0000000002 did not: it
held the whole key, and still did after a full node restart, because
every record this wallet ever wrote passed through the log.
That defeats the feature for the case it exists for, since our own
backup advice is to copy the whole directory rather than wallet.dat
alone. log_archive() cannot fix it: it only drops logs Berkeley DB
considers obsolete and never the one it is writing to, and with one 10
MB log per directory that is precisely where the keys are. They also did
not get there at encryption time -- they accumulated over the wallet's
whole life -- so nothing done during the rewrite can archive them out.
So the encrypt command now closes the environment and deletes the logs
on its way out, after the flush that makes every .dat self-contained.
This is not secure erasure; the blocks are unlinked, not overwritten.
It closes the case of someone reading a copied data directory.
Getting there uncovered a third bug, older than both.
DBFlush(true) is not safe to call twice. The first call closes the
environment, and txn_checkpoint() on a closed environment segfaults
instead of returning an error, so the try/catch around it never sees
anything. EncryptWallet() already flushes before renaming the wallet,
and the command flushes again on the way out -- that pair crashed every
single time. Guarded now.
The same function has been calling log_archive() with the address of an
uninitialised pointer since 0.1.0, on every clean shutdown, in a path
whose catch(...) cannot catch a structured fault on Windows.
DB_ARCH_REMOVE requires that argument to be NULL. Fixed while here,
because the encryption cleanup depends on that call not being a coin
flip.
---------
Co-authored-by: Gilcimar Duarte <ti@gpm.infra>
Diffstat (limited to 'src/main.cpp')
| -rw-r--r-- | src/main.cpp | 334 |
1 files changed, 332 insertions, 2 deletions
diff --git a/src/main.cpp b/src/main.cpp index efece6c..0342650 100644 --- a/src/main.cpp +++ b/src/main.cpp | |||
| @@ -72,7 +72,15 @@ vector<pair<uint256, bool> > vWalletUpdated; | |||
| 72 | CCriticalSection cs_mapWallet; | 72 | CCriticalSection cs_mapWallet; |
| 73 | 73 | ||
| 74 | map<vector<unsigned char>, CPrivKey> mapKeys; | 74 | map<vector<unsigned char>, CPrivKey> mapKeys; |
| 75 | map<vector<unsigned char>, vector<unsigned char> > mapCryptedKeys; | ||
| 75 | map<uint160, vector<unsigned char> > mapPubKeys; | 76 | map<uint160, vector<unsigned char> > mapPubKeys; |
| 77 | map<unsigned int, CWalletMasterKey> mapMasterKeys; | ||
| 78 | unsigned int nWalletMasterKeyMaxID = 0; | ||
| 79 | CKeyingMaterial vWalletMasterKey; | ||
| 80 | vector<unsigned char> vchCryptedHDMaster; | ||
| 81 | vector<unsigned char> vchCryptedHDChainCode; | ||
| 82 | bool fWalletEncrypted = false; | ||
| 83 | bool fWalletLocked = true; | ||
| 76 | CCriticalSection cs_mapKeys; | 84 | CCriticalSection cs_mapKeys; |
| 77 | CKey keyUser; | 85 | CKey keyUser; |
| 78 | 86 | ||
| @@ -159,6 +167,21 @@ int MinerThreadCount() | |||
| 159 | 167 | ||
| 160 | bool AddKey(const CKey& key) | 168 | bool AddKey(const CKey& key) |
| 161 | { | 169 | { |
| 170 | if (IsWalletEncrypted()) | ||
| 171 | { | ||
| 172 | string strError; | ||
| 173 | CPrivKey vchPrivKey = key.GetPrivKey(); | ||
| 174 | vector<unsigned char> vchCryptedSecret; | ||
| 175 | if (IsWalletLocked()) | ||
| 176 | return error("AddKey() : wallet is locked\n"); | ||
| 177 | if (!EncryptSecret(vWalletMasterKey, vector<unsigned char>(vchPrivKey.begin(), vchPrivKey.end()), | ||
| 178 | WalletKeyIV(key.GetPubKey()), vchCryptedSecret)) | ||
| 179 | return error("AddKey() : encrypting key failed\n"); | ||
| 180 | if (!AddCryptedKey(key.GetPubKey(), vchCryptedSecret)) | ||
| 181 | return false; | ||
| 182 | return CWalletDB().WriteCryptedKey(key.GetPubKey(), vchCryptedSecret); | ||
| 183 | } | ||
| 184 | |||
| 162 | CRITICAL_BLOCK(cs_mapKeys) | 185 | CRITICAL_BLOCK(cs_mapKeys) |
| 163 | { | 186 | { |
| 164 | mapKeys[key.GetPubKey()] = key.GetPrivKey(); | 187 | mapKeys[key.GetPubKey()] = key.GetPrivKey(); |
| @@ -167,10 +190,287 @@ bool AddKey(const CKey& key) | |||
| 167 | return CWalletDB().WriteKey(key.GetPubKey(), key.GetPrivKey()); | 190 | return CWalletDB().WriteKey(key.GetPubKey(), key.GetPrivKey()); |
| 168 | } | 191 | } |
| 169 | 192 | ||
| 193 | bool IsWalletEncrypted() | ||
| 194 | { | ||
| 195 | return fWalletEncrypted; | ||
| 196 | } | ||
| 197 | |||
| 198 | bool IsWalletLocked() | ||
| 199 | { | ||
| 200 | return fWalletEncrypted && fWalletLocked; | ||
| 201 | } | ||
| 202 | |||
| 203 | void LockWallet() | ||
| 204 | { | ||
| 205 | if (!fWalletEncrypted) | ||
| 206 | return; | ||
| 207 | CRITICAL_BLOCK(cs_mapKeys) | ||
| 208 | { | ||
| 209 | vWalletMasterKey.clear(); | ||
| 210 | if (!vchCryptedHDMaster.empty()) | ||
| 211 | vchHDMaster.clear(); | ||
| 212 | if (!vchCryptedHDChainCode.empty()) | ||
| 213 | vchHDChainCode.clear(); | ||
| 214 | fWalletLocked = true; | ||
| 215 | } | ||
| 216 | } | ||
| 217 | |||
| 218 | bool DeriveWalletPassphraseKey(const string& strPassphrase, | ||
| 219 | const vector<unsigned char>& vchSalt, | ||
| 220 | unsigned int nDeriveIterations, | ||
| 221 | CKeyingMaterial& vchKeyRet, | ||
| 222 | vector<unsigned char>& vchIVRet) | ||
| 223 | { | ||
| 224 | vchKeyRet.clear(); | ||
| 225 | vchIVRet.clear(); | ||
| 226 | if (vchSalt.size() != 8 || nDeriveIterations < 1) | ||
| 227 | return false; | ||
| 228 | |||
| 229 | unsigned char chKey[32]; | ||
| 230 | unsigned char chIV[16]; | ||
| 231 | int n = EVP_BytesToKey(EVP_aes_256_cbc(), EVP_sha512(), &vchSalt[0], | ||
| 232 | (const unsigned char*)strPassphrase.data(), | ||
| 233 | (int)strPassphrase.size(), | ||
| 234 | (int)nDeriveIterations, | ||
| 235 | chKey, chIV); | ||
| 236 | if (n != 32) | ||
| 237 | { | ||
| 238 | memset(chKey, 0, sizeof(chKey)); | ||
| 239 | memset(chIV, 0, sizeof(chIV)); | ||
| 240 | return false; | ||
| 241 | } | ||
| 242 | vchKeyRet.assign(chKey, chKey + sizeof(chKey)); | ||
| 243 | vchIVRet.assign(chIV, chIV + sizeof(chIV)); | ||
| 244 | memset(chKey, 0, sizeof(chKey)); | ||
| 245 | memset(chIV, 0, sizeof(chIV)); | ||
| 246 | return true; | ||
| 247 | } | ||
| 248 | |||
| 249 | bool EncryptSecret(const CKeyingMaterial& vchKey, | ||
| 250 | const vector<unsigned char>& vchPlaintext, | ||
| 251 | const vector<unsigned char>& vchIV, | ||
| 252 | vector<unsigned char>& vchCiphertextRet) | ||
| 253 | { | ||
| 254 | vchCiphertextRet.clear(); | ||
| 255 | if (vchKey.size() != 32 || vchIV.size() != 16) | ||
| 256 | return false; | ||
| 257 | |||
| 258 | EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new(); | ||
| 259 | if (!ctx) | ||
| 260 | return false; | ||
| 261 | |||
| 262 | bool fOk = false; | ||
| 263 | int nLen = 0; | ||
| 264 | int nFinal = 0; | ||
| 265 | vector<unsigned char> vchOut(vchPlaintext.size() + EVP_CIPHER_block_size(EVP_aes_256_cbc())); | ||
| 266 | if (EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, &vchKey[0], &vchIV[0]) && | ||
| 267 | EVP_EncryptUpdate(ctx, &vchOut[0], &nLen, | ||
| 268 | vchPlaintext.empty() ? NULL : &vchPlaintext[0], | ||
| 269 | (int)vchPlaintext.size()) && | ||
| 270 | EVP_EncryptFinal_ex(ctx, &vchOut[0] + nLen, &nFinal)) | ||
| 271 | { | ||
| 272 | vchOut.resize(nLen + nFinal); | ||
| 273 | vchCiphertextRet.swap(vchOut); | ||
| 274 | fOk = true; | ||
| 275 | } | ||
| 276 | EVP_CIPHER_CTX_free(ctx); | ||
| 277 | return fOk; | ||
| 278 | } | ||
| 279 | |||
| 280 | bool DecryptSecret(const CKeyingMaterial& vchKey, | ||
| 281 | const vector<unsigned char>& vchCiphertext, | ||
| 282 | const vector<unsigned char>& vchIV, | ||
| 283 | vector<unsigned char>& vchPlaintextRet) | ||
| 284 | { | ||
| 285 | vchPlaintextRet.clear(); | ||
| 286 | if (vchKey.size() != 32 || vchIV.size() != 16 || vchCiphertext.empty()) | ||
| 287 | return false; | ||
| 288 | |||
| 289 | EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new(); | ||
| 290 | if (!ctx) | ||
| 291 | return false; | ||
| 292 | |||
| 293 | bool fOk = false; | ||
| 294 | int nLen = 0; | ||
| 295 | int nFinal = 0; | ||
| 296 | vector<unsigned char> vchOut(vchCiphertext.size()); | ||
| 297 | if (EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, &vchKey[0], &vchIV[0]) && | ||
| 298 | EVP_DecryptUpdate(ctx, &vchOut[0], &nLen, &vchCiphertext[0], | ||
| 299 | (int)vchCiphertext.size()) && | ||
| 300 | EVP_DecryptFinal_ex(ctx, &vchOut[0] + nLen, &nFinal)) | ||
| 301 | { | ||
| 302 | vchOut.resize(nLen + nFinal); | ||
| 303 | vchPlaintextRet.swap(vchOut); | ||
| 304 | fOk = true; | ||
| 305 | } | ||
| 306 | EVP_CIPHER_CTX_free(ctx); | ||
| 307 | return fOk; | ||
| 308 | } | ||
| 309 | |||
| 310 | vector<unsigned char> WalletKeyIV(const vector<unsigned char>& vchPubKey) | ||
| 311 | { | ||
| 312 | uint256 hash = Hash(vchPubKey.begin(), vchPubKey.end()); | ||
| 313 | vector<unsigned char> vchIV(16); | ||
| 314 | memcpy(&vchIV[0], hash.begin(), vchIV.size()); | ||
| 315 | return vchIV; | ||
| 316 | } | ||
| 317 | |||
| 318 | vector<unsigned char> WalletSecretIV(const string& strLabel) | ||
| 319 | { | ||
| 320 | uint256 hash = Hash(strLabel.begin(), strLabel.end()); | ||
| 321 | vector<unsigned char> vchIV(16); | ||
| 322 | memcpy(&vchIV[0], hash.begin(), vchIV.size()); | ||
| 323 | return vchIV; | ||
| 324 | } | ||
| 325 | |||
| 326 | bool AddCryptedKey(const vector<unsigned char>& vchPubKey, | ||
| 327 | const vector<unsigned char>& vchCryptedSecret) | ||
| 328 | { | ||
| 329 | if (vchPubKey.empty() || vchCryptedSecret.empty()) | ||
| 330 | return false; | ||
| 331 | CRITICAL_BLOCK(cs_mapKeys) | ||
| 332 | { | ||
| 333 | fWalletEncrypted = true; | ||
| 334 | mapCryptedKeys[vchPubKey] = vchCryptedSecret; | ||
| 335 | mapPubKeys[Hash160(vchPubKey)] = vchPubKey; | ||
| 336 | } | ||
| 337 | return true; | ||
| 338 | } | ||
| 339 | |||
| 340 | bool WalletCanSpendKey(const vector<unsigned char>& vchPubKey) | ||
| 341 | { | ||
| 342 | CRITICAL_BLOCK(cs_mapKeys) | ||
| 343 | return mapKeys.count(vchPubKey) > 0 || mapCryptedKeys.count(vchPubKey) > 0; | ||
| 344 | return false; | ||
| 345 | } | ||
| 346 | |||
| 347 | bool GetWalletPrivKey(const vector<unsigned char>& vchPubKey, | ||
| 348 | CPrivKey& vchPrivKeyRet, | ||
| 349 | string& strErrorRet) | ||
| 350 | { | ||
| 351 | vchPrivKeyRet.clear(); | ||
| 352 | CRITICAL_BLOCK(cs_mapKeys) | ||
| 353 | { | ||
| 354 | map<vector<unsigned char>, CPrivKey>::iterator mi = mapKeys.find(vchPubKey); | ||
| 355 | if (mi != mapKeys.end()) | ||
| 356 | { | ||
| 357 | vchPrivKeyRet = (*mi).second; | ||
| 358 | return true; | ||
| 359 | } | ||
| 360 | |||
| 361 | map<vector<unsigned char>, vector<unsigned char> >::iterator mci = mapCryptedKeys.find(vchPubKey); | ||
| 362 | if (mci == mapCryptedKeys.end()) | ||
| 363 | { | ||
| 364 | strErrorRet = "private key not found"; | ||
| 365 | return false; | ||
| 366 | } | ||
| 367 | if (IsWalletLocked()) | ||
| 368 | { | ||
| 369 | strErrorRet = "wallet is locked"; | ||
| 370 | return false; | ||
| 371 | } | ||
| 372 | |||
| 373 | vector<unsigned char> vchPlaintext; | ||
| 374 | if (!DecryptSecret(vWalletMasterKey, (*mci).second, WalletKeyIV(vchPubKey), vchPlaintext)) | ||
| 375 | { | ||
| 376 | strErrorRet = "could not decrypt private key"; | ||
| 377 | return false; | ||
| 378 | } | ||
| 379 | |||
| 380 | CPrivKey vchPrivKey(vchPlaintext.begin(), vchPlaintext.end()); | ||
| 381 | CKey key; | ||
| 382 | if (!key.SetPrivKey(vchPrivKey) || key.GetPubKey() != vchPubKey) | ||
| 383 | { | ||
| 384 | strErrorRet = "decrypted private key does not match its public key"; | ||
| 385 | return false; | ||
| 386 | } | ||
| 387 | vchPrivKeyRet = vchPrivKey; | ||
| 388 | return true; | ||
| 389 | } | ||
| 390 | return false; | ||
| 391 | } | ||
| 392 | |||
| 393 | bool UnlockWallet(const string& strPassphrase, string& strErrorRet) | ||
| 394 | { | ||
| 395 | strErrorRet.clear(); | ||
| 396 | if (!IsWalletEncrypted()) | ||
| 397 | return true; | ||
| 398 | |||
| 399 | CRITICAL_BLOCK(cs_mapKeys) | ||
| 400 | { | ||
| 401 | for (map<unsigned int, CWalletMasterKey>::iterator mi = mapMasterKeys.begin(); | ||
| 402 | mi != mapMasterKeys.end(); ++mi) | ||
| 403 | { | ||
| 404 | const CWalletMasterKey& kMasterKey = (*mi).second; | ||
| 405 | CKeyingMaterial vchPassKey; | ||
| 406 | vector<unsigned char> vchPassIV; | ||
| 407 | if (!DeriveWalletPassphraseKey(strPassphrase, kMasterKey.vchSalt, | ||
| 408 | kMasterKey.nDeriveIterations, | ||
| 409 | vchPassKey, vchPassIV)) | ||
| 410 | continue; | ||
| 411 | |||
| 412 | vector<unsigned char> vchMasterPlain; | ||
| 413 | if (!DecryptSecret(vchPassKey, kMasterKey.vchCryptedKey, | ||
| 414 | vchPassIV, vchMasterPlain)) | ||
| 415 | continue; | ||
| 416 | if (vchMasterPlain.size() != 32) | ||
| 417 | continue; | ||
| 418 | |||
| 419 | CKeyingMaterial vchCandidate(vchMasterPlain.begin(), vchMasterPlain.end()); | ||
| 420 | bool fValid = mapCryptedKeys.empty(); | ||
| 421 | for (map<vector<unsigned char>, vector<unsigned char> >::iterator mci = mapCryptedKeys.begin(); | ||
| 422 | mci != mapCryptedKeys.end(); ++mci) | ||
| 423 | { | ||
| 424 | vector<unsigned char> vchPlaintext; | ||
| 425 | if (!DecryptSecret(vchCandidate, (*mci).second, WalletKeyIV((*mci).first), vchPlaintext)) | ||
| 426 | break; | ||
| 427 | CPrivKey vchPrivKey(vchPlaintext.begin(), vchPlaintext.end()); | ||
| 428 | CKey key; | ||
| 429 | if (key.SetPrivKey(vchPrivKey) && key.GetPubKey() == (*mci).first) | ||
| 430 | fValid = true; | ||
| 431 | break; | ||
| 432 | } | ||
| 433 | if (fValid) | ||
| 434 | { | ||
| 435 | vWalletMasterKey = vchCandidate; | ||
| 436 | if (!vchCryptedHDMaster.empty()) | ||
| 437 | { | ||
| 438 | vector<unsigned char> vchPlainHDMaster; | ||
| 439 | if (!DecryptSecret(vWalletMasterKey, vchCryptedHDMaster, | ||
| 440 | WalletSecretIV("hdmaster"), vchPlainHDMaster) || | ||
| 441 | vchPlainHDMaster.size() != 32) | ||
| 442 | { | ||
| 443 | strErrorRet = "could not decrypt HD master key"; | ||
| 444 | return false; | ||
| 445 | } | ||
| 446 | vchHDMaster = vchPlainHDMaster; | ||
| 447 | } | ||
| 448 | if (!vchCryptedHDChainCode.empty()) | ||
| 449 | { | ||
| 450 | vector<unsigned char> vchPlainHDChainCode; | ||
| 451 | if (!DecryptSecret(vWalletMasterKey, vchCryptedHDChainCode, | ||
| 452 | WalletSecretIV("hdchaincode"), vchPlainHDChainCode) || | ||
| 453 | vchPlainHDChainCode.size() != 32) | ||
| 454 | { | ||
| 455 | strErrorRet = "could not decrypt HD chain code"; | ||
| 456 | return false; | ||
| 457 | } | ||
| 458 | vchHDChainCode = vchPlainHDChainCode; | ||
| 459 | } | ||
| 460 | fWalletLocked = false; | ||
| 461 | return true; | ||
| 462 | } | ||
| 463 | } | ||
| 464 | } | ||
| 465 | |||
| 466 | strErrorRet = "passphrase did not unlock the wallet"; | ||
| 467 | return false; | ||
| 468 | } | ||
| 469 | |||
| 170 | static bool WalletAlreadyHasKey(const vector<unsigned char>& vchPubKey) | 470 | static bool WalletAlreadyHasKey(const vector<unsigned char>& vchPubKey) |
| 171 | { | 471 | { |
| 172 | CRITICAL_BLOCK(cs_mapKeys) | 472 | CRITICAL_BLOCK(cs_mapKeys) |
| 173 | return mapKeys.count(vchPubKey) > 0; | 473 | return mapKeys.count(vchPubKey) > 0 || mapCryptedKeys.count(vchPubKey) > 0; |
| 174 | return false; | 474 | return false; |
| 175 | } | 475 | } |
| 176 | 476 | ||
| @@ -509,6 +809,12 @@ bool SetHDSeedFromMnemonic(const string& strMnemonic, string& strErrorRet) | |||
| 509 | 809 | ||
| 510 | void TopUpKeyPool() | 810 | void TopUpKeyPool() |
| 511 | { | 811 | { |
| 812 | if (IsWalletLocked()) | ||
| 813 | { | ||
| 814 | printf("TopUpKeyPool() : wallet is encrypted and locked\n"); | ||
| 815 | return; | ||
| 816 | } | ||
| 817 | |||
| 512 | CRITICAL_BLOCK(cs_keyPool) | 818 | CRITICAL_BLOCK(cs_keyPool) |
| 513 | { | 819 | { |
| 514 | while ((int)mapKeyPool.size() < KEYPOOL_SIZE) | 820 | while ((int)mapKeyPool.size() < KEYPOOL_SIZE) |
| @@ -562,6 +868,12 @@ void TopUpKeyPool() | |||
| 562 | 868 | ||
| 563 | vector<unsigned char> GetKeyFromPool() | 869 | vector<unsigned char> GetKeyFromPool() |
| 564 | { | 870 | { |
| 871 | if (IsWalletLocked()) | ||
| 872 | { | ||
| 873 | printf("GetKeyFromPool() : wallet is encrypted and locked\n"); | ||
| 874 | return vector<unsigned char>(); | ||
| 875 | } | ||
| 876 | |||
| 565 | CRITICAL_BLOCK(cs_keyPool) | 877 | CRITICAL_BLOCK(cs_keyPool) |
| 566 | { | 878 | { |
| 567 | TopUpKeyPool(); | 879 | TopUpKeyPool(); |
| @@ -584,7 +896,15 @@ vector<unsigned char> GetKeyFromPool() | |||
| 584 | // locked open elsewhere. Generating on demand is what this code did for | 896 | // locked open elsewhere. Generating on demand is what this code did for |
| 585 | // its whole life, so fall back to it rather than refuse to mine. | 897 | // its whole life, so fall back to it rather than refuse to mine. |
| 586 | printf("GetKeyFromPool() : pool empty, generating a key on demand\n"); | 898 | printf("GetKeyFromPool() : pool empty, generating a key on demand\n"); |
| 587 | return GenerateNewKey(); | 899 | try |
| 900 | { | ||
| 901 | return GenerateNewKey(); | ||
| 902 | } | ||
| 903 | catch (const std::exception& e) | ||
| 904 | { | ||
| 905 | printf("GetKeyFromPool() : failed to generate a key: %s\n", e.what()); | ||
| 906 | return vector<unsigned char>(); | ||
| 907 | } | ||
| 588 | } | 908 | } |
| 589 | 909 | ||
| 590 | 910 | ||
| @@ -3581,6 +3901,11 @@ bool BitcoinMiner(int nThreadId) | |||
| 3581 | // thread happened to win a block, which meant a backup taken while mining | 3901 | // thread happened to win a block, which meant a backup taken while mining |
| 3582 | // could not spend what the very next block paid. | 3902 | // could not spend what the very next block paid. |
| 3583 | vector<unsigned char> vchPubKey = GetKeyFromPool(); | 3903 | vector<unsigned char> vchPubKey = GetKeyFromPool(); |
| 3904 | if (vchPubKey.empty()) | ||
| 3905 | { | ||
| 3906 | printf("BitcoinMiner: no wallet key available for coinbase; mining stopped\n"); | ||
| 3907 | return false; | ||
| 3908 | } | ||
| 3584 | CBigNum bnExtraNonce = 0; | 3909 | CBigNum bnExtraNonce = 0; |
| 3585 | while (fGenerateBitcoins) | 3910 | while (fGenerateBitcoins) |
| 3586 | { | 3911 | { |
| @@ -3759,6 +4084,11 @@ bool BitcoinMiner(int nThreadId) | |||
| 3759 | // them out one at a time under cs_keyPool, which is | 4084 | // them out one at a time under cs_keyPool, which is |
| 3760 | // what keeps concurrent coinbases distinct (#58). | 4085 | // what keeps concurrent coinbases distinct (#58). |
| 3761 | vchPubKey = GetKeyFromPool(); | 4086 | vchPubKey = GetKeyFromPool(); |
| 4087 | if (vchPubKey.empty()) | ||
| 4088 | { | ||
| 4089 | printf("BitcoinMiner: no replacement wallet key available; mining stopped\n"); | ||
| 4090 | fGenerateBitcoins = false; | ||
| 4091 | } | ||
| 3762 | 4092 | ||
| 3763 | // Process this block the same as if we had received it from another node | 4093 | // Process this block the same as if we had received it from another node |
| 3764 | if (!ProcessBlock(NULL, pblock.release())) | 4094 | if (!ProcessBlock(NULL, pblock.release())) |