diff --git a/doc/codebase-cleanup.md b/doc/codebase-cleanup.md index 0d4b6eec3..915e2e2a3 100644 --- a/doc/codebase-cleanup.md +++ b/doc/codebase-cleanup.md @@ -35,7 +35,12 @@ Several files are extremely large and would benefit from decomposition: | `src/chainparams.cpp` | 5,420 | Extract checkpoint data and chain-specific params to separate files | | `src/wallet/wallet.cpp` | 5,059 | Split wallet transaction logic from key management | -- [ ] Split `src/main.cpp` +- [x] Split `src/main.cpp` + - 8,217 → 2,821 lines in `main.cpp` + - Created `tx_validation.cpp` (1,012 lines) — transaction validation (IsStandardTx, CheckTransaction, ContextualCheckInputs, etc.) + - Created `mempool_accept.cpp` (524 lines) — mempool acceptance and orphan management (AcceptToMemoryPool, AddOrphanTx, etc.) + - Created `block_processing.cpp` (4,064 lines) — block processing, chain management, and disk I/O (ConnectBlock, DisconnectBlock, ActivateBestChain, CheckBlock, LoadBlockIndex, etc.) + - Created `main_internal.h` (83 lines) — shared internal state (block index candidates, file info, dirty sets, etc.) - [x] Split `src/wallet/rpcwallet.cpp` - 6,392 → 4,099 lines in `rpcwallet.cpp` - Created `wallet/rpcwallet_zindex.cpp` (1,193 lines) — z-index query RPCs (`getalldata`, `z_getinfo`, `z_getstats`, etc.) @@ -45,7 +50,9 @@ Several files are extremely large and would benefit from decomposition: - 5,420 → 593 lines in `chainparams.cpp` - Created `chainparams_checkpoints_hush3.h` (1,986 lines) — HUSH3 checkpoint data - Created `chainparams_checkpoints_dragonx.h` (2,853 lines) — DRAGONX checkpoint data -- [ ] Split `src/wallet/wallet.cpp` +- [x] Split `src/wallet/wallet.cpp` + - 5,059 → 4,143 lines in `wallet.cpp` + - Created `wallet_keys.cpp` (975 lines) — key management, encryption, HD seed, keypool ## 5. Move Implementation Out of Header Files diff --git a/src/Makefile.am b/src/Makefile.am index 085a77170..4a4f6e8f7 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -332,6 +332,7 @@ libbitcoin_wallet_a_SOURCES = \ wallet/rpcwallet_zops.cpp \ wallet/rpchushwallet.cpp \ wallet/wallet.cpp \ + wallet/wallet_keys.cpp \ wallet/wallet_ismine.cpp \ wallet/walletdb.cpp \ zcash/zip32.cpp \ diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 1017bb3df..25e06db48 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -115,359 +115,6 @@ const CWalletTx* CWallet::GetWalletTx(const uint256& hash) const return &(it->second); } - -// Generate a new Sapling spending key and return its public payment address -SaplingPaymentAddress CWallet::GenerateNewSaplingZKey(bool addToWallet) -{ - AssertLockHeld(cs_wallet); // mapSaplingZKeyMetadata - - // Create new metadata - int64_t nCreationTime = GetTime(); - CKeyMetadata metadata(nCreationTime); - - // Try to get the seed - HDSeed seed; - if (!GetHDSeed(seed)) - throw std::runtime_error("CWallet::GenerateNewSaplingZKey(): HD seed not found"); - - auto m = libzcash::SaplingExtendedSpendingKey::Master(seed); - uint32_t bip44CoinType = Params().BIP44CoinType(); - - // We use a fixed keypath scheme of m/32'/coin_type'/account' - // Derive m/32' - auto m_32h = m.Derive(32 | ZIP32_HARDENED_KEY_LIMIT); - // Derive m/32'/coin_type' - auto m_32h_cth = m_32h.Derive(bip44CoinType | ZIP32_HARDENED_KEY_LIMIT); - - // Derive account key at next index, skip keys already known to the wallet - libzcash::SaplingExtendedSpendingKey xsk; - do - { - xsk = m_32h_cth.Derive(hdChain.saplingAccountCounter | ZIP32_HARDENED_KEY_LIMIT); - metadata.hdKeypath = "m/32'/" + std::to_string(bip44CoinType) + "'/" + std::to_string(hdChain.saplingAccountCounter) + "'"; - metadata.seedFp = hdChain.seedFp; - // Increment childkey index - hdChain.saplingAccountCounter++; - } while (HaveSaplingSpendingKey(xsk.expsk.full_viewing_key())); - - // Update the chain model in the database - if (fFileBacked && !CWalletDB(strWalletFile).WriteHDChain(hdChain)) - throw std::runtime_error("CWallet::GenerateNewSaplingZKey(): Writing HD chain model failed"); - - auto ivk = xsk.expsk.full_viewing_key().in_viewing_key(); - mapSaplingZKeyMetadata[ivk] = metadata; - - auto addr = xsk.DefaultAddress(); - if (addToWallet && !AddSaplingZKey(xsk, addr)) { - throw std::runtime_error("CWallet::GenerateNewSaplingZKey(): AddSaplingZKey failed"); - } - // return default sapling payment address. - return addr; -} - -// Add spending key to keystore -bool CWallet::AddSaplingZKey( - const libzcash::SaplingExtendedSpendingKey &sk, - const libzcash::SaplingPaymentAddress &defaultAddr) -{ - AssertLockHeld(cs_wallet); // mapSaplingZKeyMetadata - - if (!CCryptoKeyStore::AddSaplingSpendingKey(sk, defaultAddr)) { - return false; - } - - nTimeFirstKey = 1; // No birthday information for viewing keys. - if (!fFileBacked) { - return true; - } - - if (!IsCrypted()) { - auto ivk = sk.expsk.full_viewing_key().in_viewing_key(); - return CWalletDB(strWalletFile).WriteSaplingZKey(ivk, sk, mapSaplingZKeyMetadata[ivk]); - } - - return true; -} - -// Add payment address -> incoming viewing key map entry -bool CWallet::AddSaplingIncomingViewingKey( - const libzcash::SaplingIncomingViewingKey &ivk, - const libzcash::SaplingPaymentAddress &addr) -{ - AssertLockHeld(cs_wallet); // mapSaplingZKeyMetadata - - if (!CCryptoKeyStore::AddSaplingIncomingViewingKey(ivk, addr)) { - return false; - } - - if (!fFileBacked) { - return true; - } - - if (!IsCrypted()) { - return CWalletDB(strWalletFile).WriteSaplingPaymentAddress(addr, ivk); - } - - return true; -} - - -CPubKey CWallet::GenerateNewKey() -{ - AssertLockHeld(cs_wallet); // mapKeyMetadata - bool fCompressed = CanSupportFeature(FEATURE_COMPRPUBKEY); // default to compressed public keys if we want 0.6.0 wallets - - CKey secret; - secret.MakeNewKey(fCompressed); - - // Compressed public keys were introduced in version 0.6.0 - if (fCompressed) - SetMinVersion(FEATURE_COMPRPUBKEY); - - CPubKey pubkey = secret.GetPubKey(); - assert(secret.VerifyPubKey(pubkey)); - - // Create new metadata - int64_t nCreationTime = GetTime(); - mapKeyMetadata[pubkey.GetID()] = CKeyMetadata(nCreationTime); - if (!nTimeFirstKey || nCreationTime < nTimeFirstKey) - nTimeFirstKey = nCreationTime; - - if (!AddKeyPubKey(secret, pubkey)) - throw std::runtime_error("CWallet::GenerateNewKey(): AddKey failed"); - return pubkey; -} - -bool CWallet::AddKeyPubKey(const CKey& secret, const CPubKey &pubkey) -{ - AssertLockHeld(cs_wallet); // mapKeyMetadata - if (!CCryptoKeyStore::AddKeyPubKey(secret, pubkey)) - return false; - - // check if we need to remove from watch-only - CScript script; - script = GetScriptForDestination(pubkey.GetID()); - if (HaveWatchOnly(script)) - RemoveWatchOnly(script); - - if (!fFileBacked) - return true; - if (!IsCrypted()) { - return CWalletDB(strWalletFile).WriteKey(pubkey, - secret.GetPrivKey(), - mapKeyMetadata[pubkey.GetID()]); - } - return true; -} - -bool CWallet::AddCryptedKey(const CPubKey &vchPubKey, - const vector &vchCryptedSecret) -{ - - if (!CCryptoKeyStore::AddCryptedKey(vchPubKey, vchCryptedSecret)) - return false; - if (!fFileBacked) - return true; - { - LOCK(cs_wallet); - if (pwalletdbEncryption) - return pwalletdbEncryption->WriteCryptedKey(vchPubKey, - vchCryptedSecret, - mapKeyMetadata[vchPubKey.GetID()]); - else - return CWalletDB(strWalletFile).WriteCryptedKey(vchPubKey, - vchCryptedSecret, - mapKeyMetadata[vchPubKey.GetID()]); - } - return false; -} - -bool CWallet::AddCryptedSaplingSpendingKey(const libzcash::SaplingExtendedFullViewingKey &extfvk, - const std::vector &vchCryptedSecret, - const libzcash::SaplingPaymentAddress &defaultAddr) -{ - if (!CCryptoKeyStore::AddCryptedSaplingSpendingKey(extfvk, vchCryptedSecret, defaultAddr)) - return false; - if (!fFileBacked) - return true; - { - LOCK(cs_wallet); - if (pwalletdbEncryption) { - return pwalletdbEncryption->WriteCryptedSaplingZKey(extfvk, - vchCryptedSecret, - mapSaplingZKeyMetadata[extfvk.fvk.in_viewing_key()]); - } else { - return CWalletDB(strWalletFile).WriteCryptedSaplingZKey(extfvk, - vchCryptedSecret, - mapSaplingZKeyMetadata[extfvk.fvk.in_viewing_key()]); - } - } - return false; -} - -bool CWallet::LoadKeyMetadata(const CPubKey &pubkey, const CKeyMetadata &meta) -{ - AssertLockHeld(cs_wallet); // mapKeyMetadata - if (meta.nCreateTime && (!nTimeFirstKey || meta.nCreateTime < nTimeFirstKey)) - nTimeFirstKey = meta.nCreateTime; - - mapKeyMetadata[pubkey.GetID()] = meta; - return true; -} - -bool CWallet::LoadCryptedKey(const CPubKey &vchPubKey, const std::vector &vchCryptedSecret) -{ - return CCryptoKeyStore::AddCryptedKey(vchPubKey, vchCryptedSecret); -} - - -bool CWallet::LoadCryptedSaplingZKey( - const libzcash::SaplingExtendedFullViewingKey &extfvk, - const std::vector &vchCryptedSecret) -{ - return CCryptoKeyStore::AddCryptedSaplingSpendingKey(extfvk, vchCryptedSecret, extfvk.DefaultAddress()); -} - -bool CWallet::LoadSaplingZKeyMetadata(const libzcash::SaplingIncomingViewingKey &ivk, const CKeyMetadata &meta) -{ - AssertLockHeld(cs_wallet); // mapSaplingZKeyMetadata - mapSaplingZKeyMetadata[ivk] = meta; - return true; -} - -bool CWallet::LoadSaplingZKey(const libzcash::SaplingExtendedSpendingKey &key) -{ - return CCryptoKeyStore::AddSaplingSpendingKey(key, key.DefaultAddress()); -} - -bool CWallet::LoadSaplingPaymentAddress( - const libzcash::SaplingPaymentAddress &addr, - const libzcash::SaplingIncomingViewingKey &ivk) -{ - return CCryptoKeyStore::AddSaplingIncomingViewingKey(ivk, addr); -} - -bool CWallet::AddCScript(const CScript& redeemScript) -{ - if (!CCryptoKeyStore::AddCScript(redeemScript)) - return false; - if (!fFileBacked) - return true; - return CWalletDB(strWalletFile).WriteCScript(Hash160(redeemScript), redeemScript); -} - -bool CWallet::LoadCScript(const CScript& redeemScript) -{ - /* A sanity check was added in pull #3843 to avoid adding redeemScripts - * that never can be redeemed. However, old wallets may still contain - * these. Do not add them to the wallet and warn. */ - if (redeemScript.size() > MAX_SCRIPT_ELEMENT_SIZE) - { - std::string strAddr = EncodeDestination(CScriptID(redeemScript)); - LogPrintf("%s: Warning: This wallet contains a redeemScript of size %i which exceeds maximum size %i thus can never be redeemed. Do not use address %s.\n", - __func__, redeemScript.size(), MAX_SCRIPT_ELEMENT_SIZE, strAddr); - return true; - } - - return CCryptoKeyStore::AddCScript(redeemScript); -} - -bool CWallet::AddWatchOnly(const CScript &dest) -{ - if (!CCryptoKeyStore::AddWatchOnly(dest)) - return false; - nTimeFirstKey = 1; // No birthday information for watch-only keys. - NotifyWatchonlyChanged(true); - if (!fFileBacked) - return true; - return CWalletDB(strWalletFile).WriteWatchOnly(dest); -} - -bool CWallet::RemoveWatchOnly(const CScript &dest) -{ - AssertLockHeld(cs_wallet); - if (!CCryptoKeyStore::RemoveWatchOnly(dest)) - return false; - if (!HaveWatchOnly()) - NotifyWatchonlyChanged(false); - if (fFileBacked) - if (!CWalletDB(strWalletFile).EraseWatchOnly(dest)) - return false; - - return true; -} - -bool CWallet::LoadWatchOnly(const CScript &dest) -{ - return CCryptoKeyStore::AddWatchOnly(dest); -} - -bool CWallet::Unlock(const SecureString& strWalletPassphrase) -{ - CCrypter crypter; - CKeyingMaterial vMasterKey; - - { - LOCK(cs_wallet); - BOOST_FOREACH(const MasterKeyMap::value_type& pMasterKey, mapMasterKeys) - { - if(!crypter.SetKeyFromPassphrase(strWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod)) - return false; - if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, vMasterKey)) - continue; // try another master key - if (CCryptoKeyStore::Unlock(vMasterKey)) - return true; - } - } - return false; -} - -bool CWallet::ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase, const SecureString& strNewWalletPassphrase) -{ - bool fWasLocked = IsLocked(); - - { - LOCK(cs_wallet); - Lock(); - - CCrypter crypter; - CKeyingMaterial vMasterKey; - BOOST_FOREACH(MasterKeyMap::value_type& pMasterKey, mapMasterKeys) - { - if(!crypter.SetKeyFromPassphrase(strOldWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod)) - return false; - if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, vMasterKey)) - return false; - if (CCryptoKeyStore::Unlock(vMasterKey)) - { - int64_t nStartTime = GetTimeMillis(); - crypter.SetKeyFromPassphrase(strNewWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod); - pMasterKey.second.nDeriveIterations = pMasterKey.second.nDeriveIterations * (100 / ((double)(GetTimeMillis() - nStartTime))); - - nStartTime = GetTimeMillis(); - crypter.SetKeyFromPassphrase(strNewWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod); - pMasterKey.second.nDeriveIterations = (pMasterKey.second.nDeriveIterations + pMasterKey.second.nDeriveIterations * 100 / ((double)(GetTimeMillis() - nStartTime))) / 2; - - if (pMasterKey.second.nDeriveIterations < 25000) - pMasterKey.second.nDeriveIterations = 25000; - - LogPrintf("Wallet passphrase changed to an nDeriveIterations of %i\n", pMasterKey.second.nDeriveIterations); - - if (!crypter.SetKeyFromPassphrase(strNewWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod)) - return false; - if (!crypter.Encrypt(vMasterKey, pMasterKey.second.vchCryptedKey)) - return false; - CWalletDB(strWalletFile).WriteMasterKey(pMasterKey.first, pMasterKey.second); - if (fWasLocked) - Lock(); - return true; - } - } - } - - return false; -} - void CWallet::ChainTip(const CBlockIndex *pindex, const CBlock *pblock, boost::optional> added) @@ -1216,97 +863,6 @@ void CWallet::BuildWitnessCache(const CBlockIndex* pindex, bool witnessOnly) } -bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase) -{ - if (IsCrypted()) - return false; - - CKeyingMaterial vMasterKey; - - vMasterKey.resize(WALLET_CRYPTO_KEY_SIZE); - GetRandBytes(&vMasterKey[0], WALLET_CRYPTO_KEY_SIZE); - - CMasterKey kMasterKey; - - kMasterKey.vchSalt.resize(WALLET_CRYPTO_SALT_SIZE); - GetRandBytes(&kMasterKey.vchSalt[0], WALLET_CRYPTO_SALT_SIZE); - - CCrypter crypter; - int64_t nStartTime = GetTimeMillis(); - crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt, 25000, kMasterKey.nDerivationMethod); - kMasterKey.nDeriveIterations = 2500000 / ((double)(GetTimeMillis() - nStartTime)); - - nStartTime = GetTimeMillis(); - crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt, kMasterKey.nDeriveIterations, kMasterKey.nDerivationMethod); - kMasterKey.nDeriveIterations = (kMasterKey.nDeriveIterations + kMasterKey.nDeriveIterations * 100 / ((double)(GetTimeMillis() - nStartTime))) / 2; - - if (kMasterKey.nDeriveIterations < 25000) - kMasterKey.nDeriveIterations = 25000; - - LogPrintf("Encrypting Wallet with an nDeriveIterations of %i\n", kMasterKey.nDeriveIterations); - - if (!crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt, kMasterKey.nDeriveIterations, kMasterKey.nDerivationMethod)) - return false; - if (!crypter.Encrypt(vMasterKey, kMasterKey.vchCryptedKey)) - return false; - - { - LOCK(cs_wallet); - mapMasterKeys[++nMasterKeyMaxID] = kMasterKey; - if (fFileBacked) - { - assert(!pwalletdbEncryption); - pwalletdbEncryption = new CWalletDB(strWalletFile); - if (!pwalletdbEncryption->TxnBegin()) { - delete pwalletdbEncryption; - pwalletdbEncryption = NULL; - return false; - } - pwalletdbEncryption->WriteMasterKey(nMasterKeyMaxID, kMasterKey); - } - - if (!EncryptKeys(vMasterKey)) - { - if (fFileBacked) { - pwalletdbEncryption->TxnAbort(); - delete pwalletdbEncryption; - } - // We now probably have half of our keys encrypted in memory, and half not... - // die and let the user reload the unencrypted wallet. - assert(false); - } - - // Encryption was introduced in version 0.4.0 - SetMinVersion(FEATURE_WALLETCRYPT, pwalletdbEncryption, true); - - if (fFileBacked) - { - if (!pwalletdbEncryption->TxnCommit()) { - delete pwalletdbEncryption; - // We now have keys encrypted in memory, but not on disk... - // die to avoid confusion and let the user reload the unencrypted wallet. - assert(false); - } - - delete pwalletdbEncryption; - pwalletdbEncryption = NULL; - } - - Lock(); - Unlock(strWalletPassphrase); - NewKeyPool(); - Lock(); - - // Need to completely rewrite the wallet file; if we don't, bdb might keep - // bits of the unencrypted private key in slack space in the database file. - CDB::Rewrite(strWalletFile); - - } - NotifyStatusChanged(this); - - return true; -} - int64_t CWallet::IncOrderPosNext(CWalletDB *pwalletdb) { AssertLockHeld(cs_wallet); // nOrderPosNext @@ -2110,92 +1666,6 @@ CAmount CWallet::GetChange(const CTransaction& tx) const return nChange; } -bool CWallet::IsHDFullyEnabled() const -{ - // Only Sapling addresses are HD for now - return false; -} - -void CWallet::GenerateNewSeed() -{ - LOCK(cs_wallet); - - auto seed = HDSeed::Random(HD_WALLET_SEED_LENGTH); - - int64_t nCreationTime = GetTime(); - - // If the wallet is encrypted and locked, this will fail. - if (!SetHDSeed(seed)) - throw std::runtime_error(std::string(__func__) + ": SetHDSeed failed"); - - // store the key creation time together with - // the child index counter in the database - // as a hdchain object - CHDChain newHdChain; - newHdChain.nVersion = CHDChain::VERSION_HD_BASE; - newHdChain.seedFp = seed.Fingerprint(); - newHdChain.nCreateTime = nCreationTime; - SetHDChain(newHdChain, false); -} - -bool CWallet::SetHDSeed(const HDSeed& seed) -{ - if (!CCryptoKeyStore::SetHDSeed(seed)) { - return false; - } - - if (!fFileBacked) { - return true; - } - - { - LOCK(cs_wallet); - if (!IsCrypted()) { - return CWalletDB(strWalletFile).WriteHDSeed(seed); - } - } - return true; -} - -bool CWallet::SetCryptedHDSeed(const uint256& seedFp, const std::vector &vchCryptedSecret) -{ - if (!CCryptoKeyStore::SetCryptedHDSeed(seedFp, vchCryptedSecret)) { - return false; - } - - if (!fFileBacked) { - return true; - } - - { - LOCK(cs_wallet); - if (pwalletdbEncryption) - return pwalletdbEncryption->WriteCryptedHDSeed(seedFp, vchCryptedSecret); - else - return CWalletDB(strWalletFile).WriteCryptedHDSeed(seedFp, vchCryptedSecret); - } - return false; -} - -void CWallet::SetHDChain(const CHDChain& chain, bool memonly) -{ - LOCK(cs_wallet); - if (!memonly && fFileBacked && !CWalletDB(strWalletFile).WriteHDChain(chain)) - throw std::runtime_error(std::string(__func__) + ": writing chain failed"); - - hdChain = chain; -} - -bool CWallet::LoadHDSeed(const HDSeed& seed) -{ - return CBasicKeyStore::SetHDSeed(seed); -} - -bool CWallet::LoadCryptedHDSeed(const uint256& seedFp, const std::vector& seed) -{ - return CCryptoKeyStore::SetCryptedHDSeed(seedFp, seed); -} - void CWalletTx::SetSaplingNoteData(mapSaplingNoteData_t ¬eData) { mapSaplingNoteData.clear(); @@ -4196,143 +3666,6 @@ bool CWallet::SetDefaultKey(const CPubKey &vchPubKey) return true; } -/** - * Mark old keypool keys as used, - * and generate all new keys - */ -bool CWallet::NewKeyPool() -{ - { - LOCK(cs_wallet); - CWalletDB walletdb(strWalletFile); - BOOST_FOREACH(int64_t nIndex, setKeyPool) - walletdb.ErasePool(nIndex); - setKeyPool.clear(); - - if (IsLocked()) - return false; - - int64_t nKeys = max(GetArg("-keypool", 100), (int64_t)0); - for (int i = 0; i < nKeys; i++) - { - int64_t nIndex = i+1; - walletdb.WritePool(nIndex, CKeyPool(GenerateNewKey())); - setKeyPool.insert(nIndex); - } - LogPrintf("CWallet::NewKeyPool wrote %d new keys\n", nKeys); - } - return true; -} - -bool CWallet::TopUpKeyPool(unsigned int kpSize) -{ - { - LOCK(cs_wallet); - - if (IsLocked()) - return false; - - CWalletDB walletdb(strWalletFile); - - // Top up key pool - unsigned int nTargetSize; - if (kpSize > 0) - nTargetSize = kpSize; - else - nTargetSize = max(GetArg("-keypool", 100), (int64_t) 0); - - while (setKeyPool.size() < (nTargetSize + 1)) - { - int64_t nEnd = 1; - if (!setKeyPool.empty()) - nEnd = *(--setKeyPool.end()) + 1; - if (!walletdb.WritePool(nEnd, CKeyPool(GenerateNewKey()))) - throw runtime_error("TopUpKeyPool(): writing generated key failed"); - setKeyPool.insert(nEnd); - LogPrintf("keypool added key %d, size=%u\n", nEnd, setKeyPool.size()); - } - } - return true; -} - -void CWallet::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool) -{ - nIndex = -1; - keypool.vchPubKey = CPubKey(); - { - LOCK(cs_wallet); - - if (!IsLocked()) - TopUpKeyPool(); - - // Get the oldest key - if(setKeyPool.empty()) - return; - - CWalletDB walletdb(strWalletFile); - - nIndex = *(setKeyPool.begin()); - setKeyPool.erase(setKeyPool.begin()); - if (!walletdb.ReadPool(nIndex, keypool)) - throw runtime_error("ReserveKeyFromKeyPool(): read failed"); - if (!HaveKey(keypool.vchPubKey.GetID())) - throw runtime_error("ReserveKeyFromKeyPool(): unknown key in key pool"); - assert(keypool.vchPubKey.IsValid()); - //LogPrintf("keypool reserve %d\n", nIndex); - } -} - -void CWallet::KeepKey(int64_t nIndex) -{ - // Remove from key pool - if (fFileBacked) - { - CWalletDB walletdb(strWalletFile); - walletdb.ErasePool(nIndex); - } - LogPrintf("keypool keep %d\n", nIndex); -} - -void CWallet::ReturnKey(int64_t nIndex) -{ - // Return to key pool - { - LOCK(cs_wallet); - setKeyPool.insert(nIndex); - } - //LogPrintf("keypool return %d\n", nIndex); -} - -bool CWallet::GetKeyFromPool(CPubKey& result) -{ - int64_t nIndex = 0; - CKeyPool keypool; - { - LOCK(cs_wallet); - ReserveKeyFromKeyPool(nIndex, keypool); - if (nIndex == -1) - { - if (IsLocked()) return false; - result = GenerateNewKey(); - return true; - } - KeepKey(nIndex); - result = keypool.vchPubKey; - } - return true; -} - -int64_t CWallet::GetOldestKeyPoolTime() -{ - int64_t nIndex = 0; - CKeyPool keypool; - ReserveKeyFromKeyPool(nIndex, keypool); - if (nIndex == -1) - return GetTime(); - ReturnKey(nIndex); - return keypool.nTime; -} - std::map CWallet::GetAddressBalances() { map balances; @@ -4480,59 +3813,6 @@ std::set CWallet::GetAccountAddresses(const std::string& strAcco return result; } -bool CReserveKey::GetReservedKey(CPubKey& pubkey) -{ - if (nIndex == -1) - { - CKeyPool keypool; - pwallet->ReserveKeyFromKeyPool(nIndex, keypool); - if (nIndex != -1) - vchPubKey = keypool.vchPubKey; - else { - return false; - } - } - assert(vchPubKey.IsValid()); - pubkey = vchPubKey; - return true; -} - -void CReserveKey::KeepKey() -{ - if (nIndex != -1) - pwallet->KeepKey(nIndex); - nIndex = -1; - vchPubKey = CPubKey(); -} - -void CReserveKey::ReturnKey() -{ - if (nIndex != -1) - pwallet->ReturnKey(nIndex); - nIndex = -1; - vchPubKey = CPubKey(); -} - -void CWallet::GetAllReserveKeys(set& setAddress) const -{ - setAddress.clear(); - - CWalletDB walletdb(strWalletFile); - - LOCK2(cs_main, cs_wallet); - BOOST_FOREACH(const int64_t& id, setKeyPool) - { - CKeyPool keypool; - if (!walletdb.ReadPool(id, keypool)) - throw runtime_error("GetAllReserveKeyHashes(): read failed"); - assert(keypool.vchPubKey.IsValid()); - CKeyID keyID = keypool.vchPubKey.GetID(); - if (!HaveKey(keyID)) - throw runtime_error("GetAllReserveKeyHashes(): unknown key in key pool"); - setAddress.insert(keyID); - } -} - void CWallet::UpdatedTransaction(const uint256 &hashTx) { { @@ -4616,96 +3896,6 @@ std::vector CWallet::ListLockedSaplingNotes() /** @} */ // end of Actions -class CAffectedKeysVisitor : public boost::static_visitor { -private: - const CKeyStore &keystore; - std::vector &vKeys; - -public: - CAffectedKeysVisitor(const CKeyStore &keystoreIn, std::vector &vKeysIn) : keystore(keystoreIn), vKeys(vKeysIn) {} - - void Process(const CScript &script) { - txnouttype type; - std::vector vDest; - int nRequired; - if (ExtractDestinations(script, type, vDest, nRequired)) { - BOOST_FOREACH(const CTxDestination &dest, vDest) - boost::apply_visitor(*this, dest); - } - } - - void operator()(const CKeyID &keyId) { - if (keystore.HaveKey(keyId)) - vKeys.push_back(keyId); - } - - void operator()(const CPubKey &key) { - CKeyID keyId = key.GetID(); - if (keystore.HaveKey(keyId)) - vKeys.push_back(keyId); - } - - void operator()(const CScriptID &scriptId) { - CScript script; - if (keystore.GetCScript(scriptId, script)) - Process(script); - } - - void operator()(const CNoDestination &none) {} -}; - -void CWallet::GetKeyBirthTimes(std::map &mapKeyBirth) const { - AssertLockHeld(cs_wallet); // mapKeyMetadata - mapKeyBirth.clear(); - - // get birth times for keys with metadata - for (std::map::const_iterator it = mapKeyMetadata.begin(); it != mapKeyMetadata.end(); it++) - if (it->second.nCreateTime) - mapKeyBirth[it->first] = it->second.nCreateTime; - - // map in which we'll infer heights of other keys - CBlockIndex *pindexMax = chainActive[std::max(0, chainActive.Height() - 144)]; // the tip can be reorganised; use a 144-block safety margin - std::map mapKeyFirstBlock; - std::set setKeys; - GetKeys(setKeys); - BOOST_FOREACH(const CKeyID &keyid, setKeys) { - if (mapKeyBirth.count(keyid) == 0) - mapKeyFirstBlock[keyid] = pindexMax; - } - setKeys.clear(); - - // if there are no such keys, we're done - if (mapKeyFirstBlock.empty()) - return; - - // find first block that affects those keys, if there are any left - std::vector vAffected; - for (std::map::const_iterator it = mapWallet.begin(); it != mapWallet.end(); it++) { - // iterate over all wallet transactions... - const CWalletTx &wtx = (*it).second; - BlockMap::const_iterator blit = mapBlockIndex.find(wtx.hashBlock); - if (blit != mapBlockIndex.end() && chainActive.Contains(blit->second)) { - // ... which are already in a block - int nHeight = blit->second->GetHeight(); - BOOST_FOREACH(const CTxOut &txout, wtx.vout) { - // iterate over all their outputs - CAffectedKeysVisitor(*this, vAffected).Process(txout.scriptPubKey); - BOOST_FOREACH(const CKeyID &keyid, vAffected) { - // ... and all their affected keys - std::map::iterator rit = mapKeyFirstBlock.find(keyid); - if (rit != mapKeyFirstBlock.end() && nHeight < rit->second->GetHeight()) - rit->second = blit->second; - } - vAffected.clear(); - } - } - } - - // Extract block timestamps for those keys - for (std::map::const_iterator it = mapKeyFirstBlock.begin(); it != mapKeyFirstBlock.end(); it++) - mapKeyBirth[it->first] = it->second->GetBlockTime() - 7200; // block times can be 2h off -} - bool CWallet::AddDestData(const CTxDestination &dest, const std::string &key, const std::string &value) { if (boost::get(&dest)) @@ -4951,109 +4141,3 @@ void CWallet::GetFilteredNotes( } } } - - -// -// Shielded key and address generalizations -// - -bool IncomingViewingKeyBelongsToWallet::operator()(const libzcash::SaplingPaymentAddress &zaddr) const -{ - libzcash::SaplingIncomingViewingKey ivk; - return m_wallet->GetSaplingIncomingViewingKey(zaddr, ivk); -} - -bool IncomingViewingKeyBelongsToWallet::operator()(const libzcash::InvalidEncoding& no) const -{ - return false; -} - -bool PaymentAddressBelongsToWallet::operator()(const libzcash::SaplingPaymentAddress &zaddr) const -{ - libzcash::SaplingIncomingViewingKey ivk; - - // If we have a SaplingExtendedSpendingKey in the wallet, then we will - // also have the corresponding SaplingFullViewingKey. - return m_wallet->GetSaplingIncomingViewingKey(zaddr, ivk) && - m_wallet->HaveSaplingFullViewingKey(ivk); -} - -bool PaymentAddressBelongsToWallet::operator()(const libzcash::InvalidEncoding& no) const -{ - return false; -} - -bool HaveSpendingKeyForPaymentAddress::operator()(const libzcash::SaplingPaymentAddress &zaddr) const -{ - libzcash::SaplingIncomingViewingKey ivk; - libzcash::SaplingFullViewingKey fvk; - - return m_wallet->GetSaplingIncomingViewingKey(zaddr, ivk) && - m_wallet->GetSaplingFullViewingKey(ivk, fvk) && - m_wallet->HaveSaplingSpendingKey(fvk); -} - -bool HaveSpendingKeyForPaymentAddress::operator()(const libzcash::InvalidEncoding& no) const -{ - return false; -} - -boost::optional GetSpendingKeyForPaymentAddress::operator()( - const libzcash::SaplingPaymentAddress &zaddr) const -{ - libzcash::SaplingExtendedSpendingKey extsk; - if (m_wallet->GetSaplingExtendedSpendingKey(zaddr, extsk)) { - return libzcash::SpendingKey(extsk); - } else { - return boost::none; - } -} - -boost::optional GetSpendingKeyForPaymentAddress::operator()( - const libzcash::InvalidEncoding& no) const -{ - // Defaults to InvalidEncoding - return libzcash::SpendingKey(); -} - - -SpendingKeyAddResult AddSpendingKeyToWallet::operator()(const libzcash::SaplingExtendedSpendingKey &sk) const { - auto fvk = sk.expsk.full_viewing_key(); - auto ivk = fvk.in_viewing_key(); - auto addr = sk.DefaultAddress(); - { - if (log){ - LogPrint("zrpc", "Importing zaddr %s...\n", EncodePaymentAddress(addr)); - } - // Don't throw error in case a key is already there - if (m_wallet->HaveSaplingSpendingKey(fvk)) { - return KeyAlreadyExists; - } else { - if (!m_wallet-> AddSaplingZKey(sk, addr)) { - return KeyNotAdded; - } - - // Sapling addresses can't have been used in transactions prior to activation. - if (params.vUpgrades[Consensus::UPGRADE_SAPLING].nActivationHeight == Consensus::NetworkUpgrade::ALWAYS_ACTIVE) { - m_wallet->mapSaplingZKeyMetadata[ivk].nCreateTime = nTime; - } else { - // TODO: set a better time for HUSH+DRAGONX - // 154051200 seconds from epoch is Friday, 26 October 2018 00:00:00 GMT - definitely before Sapling activates - m_wallet->mapSaplingZKeyMetadata[ivk].nCreateTime = std::max((int64_t) 154051200, nTime); - } - if (hdKeypath) { - m_wallet->mapSaplingZKeyMetadata[ivk].hdKeypath = hdKeypath.get(); - } - if (seedFpStr) { - uint256 seedFp; - seedFp.SetHex(seedFpStr.get()); - m_wallet->mapSaplingZKeyMetadata[ivk].seedFp = seedFp; - } - return KeyAdded; - } - } -} - -SpendingKeyAddResult AddSpendingKeyToWallet::operator()(const libzcash::InvalidEncoding& no) const { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid spending key"); -} diff --git a/src/wallet/wallet_keys.cpp b/src/wallet/wallet_keys.cpp new file mode 100644 index 000000000..3b3e0b351 --- /dev/null +++ b/src/wallet/wallet_keys.cpp @@ -0,0 +1,975 @@ +// Copyright (c) 2009-2010 Satoshi Nakamoto +// Copyright (c) 2009-2014 The Bitcoin Core developers +// Copyright (c) 2016-2024 The Hush developers +// Distributed under the GPLv3 software license, see the accompanying +// file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html + +/****************************************************************************** + * Copyright © 2014-2019 The SuperNET Developers. * + * * + * See the AUTHORS, DEVELOPER-AGREEMENT and LICENSE files at * + * the top-level directory of this distribution for the individual copyright * + * holder information and the developer policies on copyright and licensing. * + * * + * Unless otherwise agreed in a custom licensing agreement, no part of the * + * SuperNET software, including this file may be copied, modified, propagated * + * or distributed except according to the terms contained in the LICENSE file * + * * + * Removal or modification of this copyright notice is prohibited. * + * * + ******************************************************************************/ + +// Key management, encryption, HD wallet, and keypool operations +// Split from wallet.cpp to separate key management from transaction logic + +#include "wallet/wallet.h" +#include "key_io.h" +#include "main.h" +#include "rpc/protocol.h" +#include "script/script.h" +#include "script/sign.h" +#include "crypter.h" +#include "consensus/upgrades.h" +#include "zcash/zip32.h" + +#include +#include +#include +#include + +using namespace std; +using namespace libzcash; + +// +// Key generation and management +// + +// Generate a new Sapling spending key and return its public payment address +SaplingPaymentAddress CWallet::GenerateNewSaplingZKey(bool addToWallet) +{ + AssertLockHeld(cs_wallet); // mapSaplingZKeyMetadata + + // Create new metadata + int64_t nCreationTime = GetTime(); + CKeyMetadata metadata(nCreationTime); + + // Try to get the seed + HDSeed seed; + if (!GetHDSeed(seed)) + throw std::runtime_error("CWallet::GenerateNewSaplingZKey(): HD seed not found"); + + auto m = libzcash::SaplingExtendedSpendingKey::Master(seed); + uint32_t bip44CoinType = Params().BIP44CoinType(); + + // We use a fixed keypath scheme of m/32'/coin_type'/account' + // Derive m/32' + auto m_32h = m.Derive(32 | ZIP32_HARDENED_KEY_LIMIT); + // Derive m/32'/coin_type' + auto m_32h_cth = m_32h.Derive(bip44CoinType | ZIP32_HARDENED_KEY_LIMIT); + + // Derive account key at next index, skip keys already known to the wallet + libzcash::SaplingExtendedSpendingKey xsk; + do + { + xsk = m_32h_cth.Derive(hdChain.saplingAccountCounter | ZIP32_HARDENED_KEY_LIMIT); + metadata.hdKeypath = "m/32'/" + std::to_string(bip44CoinType) + "'/" + std::to_string(hdChain.saplingAccountCounter) + "'"; + metadata.seedFp = hdChain.seedFp; + // Increment childkey index + hdChain.saplingAccountCounter++; + } while (HaveSaplingSpendingKey(xsk.expsk.full_viewing_key())); + + // Update the chain model in the database + if (fFileBacked && !CWalletDB(strWalletFile).WriteHDChain(hdChain)) + throw std::runtime_error("CWallet::GenerateNewSaplingZKey(): Writing HD chain model failed"); + + auto ivk = xsk.expsk.full_viewing_key().in_viewing_key(); + mapSaplingZKeyMetadata[ivk] = metadata; + + auto addr = xsk.DefaultAddress(); + if (addToWallet && !AddSaplingZKey(xsk, addr)) { + throw std::runtime_error("CWallet::GenerateNewSaplingZKey(): AddSaplingZKey failed"); + } + // return default sapling payment address. + return addr; +} + +// Add spending key to keystore +bool CWallet::AddSaplingZKey( + const libzcash::SaplingExtendedSpendingKey &sk, + const libzcash::SaplingPaymentAddress &defaultAddr) +{ + AssertLockHeld(cs_wallet); // mapSaplingZKeyMetadata + + if (!CCryptoKeyStore::AddSaplingSpendingKey(sk, defaultAddr)) { + return false; + } + + nTimeFirstKey = 1; // No birthday information for viewing keys. + if (!fFileBacked) { + return true; + } + + if (!IsCrypted()) { + auto ivk = sk.expsk.full_viewing_key().in_viewing_key(); + return CWalletDB(strWalletFile).WriteSaplingZKey(ivk, sk, mapSaplingZKeyMetadata[ivk]); + } + + return true; +} + +// Add payment address -> incoming viewing key map entry +bool CWallet::AddSaplingIncomingViewingKey( + const libzcash::SaplingIncomingViewingKey &ivk, + const libzcash::SaplingPaymentAddress &addr) +{ + AssertLockHeld(cs_wallet); // mapSaplingZKeyMetadata + + if (!CCryptoKeyStore::AddSaplingIncomingViewingKey(ivk, addr)) { + return false; + } + + if (!fFileBacked) { + return true; + } + + if (!IsCrypted()) { + return CWalletDB(strWalletFile).WriteSaplingPaymentAddress(addr, ivk); + } + + return true; +} + + +CPubKey CWallet::GenerateNewKey() +{ + AssertLockHeld(cs_wallet); // mapKeyMetadata + bool fCompressed = CanSupportFeature(FEATURE_COMPRPUBKEY); // default to compressed public keys if we want 0.6.0 wallets + + CKey secret; + secret.MakeNewKey(fCompressed); + + // Compressed public keys were introduced in version 0.6.0 + if (fCompressed) + SetMinVersion(FEATURE_COMPRPUBKEY); + + CPubKey pubkey = secret.GetPubKey(); + assert(secret.VerifyPubKey(pubkey)); + + // Create new metadata + int64_t nCreationTime = GetTime(); + mapKeyMetadata[pubkey.GetID()] = CKeyMetadata(nCreationTime); + if (!nTimeFirstKey || nCreationTime < nTimeFirstKey) + nTimeFirstKey = nCreationTime; + + if (!AddKeyPubKey(secret, pubkey)) + throw std::runtime_error("CWallet::GenerateNewKey(): AddKey failed"); + return pubkey; +} + +bool CWallet::AddKeyPubKey(const CKey& secret, const CPubKey &pubkey) +{ + AssertLockHeld(cs_wallet); // mapKeyMetadata + if (!CCryptoKeyStore::AddKeyPubKey(secret, pubkey)) + return false; + + // check if we need to remove from watch-only + CScript script; + script = GetScriptForDestination(pubkey.GetID()); + if (HaveWatchOnly(script)) + RemoveWatchOnly(script); + + if (!fFileBacked) + return true; + if (!IsCrypted()) { + return CWalletDB(strWalletFile).WriteKey(pubkey, + secret.GetPrivKey(), + mapKeyMetadata[pubkey.GetID()]); + } + return true; +} + +bool CWallet::AddCryptedKey(const CPubKey &vchPubKey, + const vector &vchCryptedSecret) +{ + + if (!CCryptoKeyStore::AddCryptedKey(vchPubKey, vchCryptedSecret)) + return false; + if (!fFileBacked) + return true; + { + LOCK(cs_wallet); + if (pwalletdbEncryption) + return pwalletdbEncryption->WriteCryptedKey(vchPubKey, + vchCryptedSecret, + mapKeyMetadata[vchPubKey.GetID()]); + else + return CWalletDB(strWalletFile).WriteCryptedKey(vchPubKey, + vchCryptedSecret, + mapKeyMetadata[vchPubKey.GetID()]); + } + return false; +} + +bool CWallet::AddCryptedSaplingSpendingKey(const libzcash::SaplingExtendedFullViewingKey &extfvk, + const std::vector &vchCryptedSecret, + const libzcash::SaplingPaymentAddress &defaultAddr) +{ + if (!CCryptoKeyStore::AddCryptedSaplingSpendingKey(extfvk, vchCryptedSecret, defaultAddr)) + return false; + if (!fFileBacked) + return true; + { + LOCK(cs_wallet); + if (pwalletdbEncryption) { + return pwalletdbEncryption->WriteCryptedSaplingZKey(extfvk, + vchCryptedSecret, + mapSaplingZKeyMetadata[extfvk.fvk.in_viewing_key()]); + } else { + return CWalletDB(strWalletFile).WriteCryptedSaplingZKey(extfvk, + vchCryptedSecret, + mapSaplingZKeyMetadata[extfvk.fvk.in_viewing_key()]); + } + } + return false; +} + +bool CWallet::LoadKeyMetadata(const CPubKey &pubkey, const CKeyMetadata &meta) +{ + AssertLockHeld(cs_wallet); // mapKeyMetadata + if (meta.nCreateTime && (!nTimeFirstKey || meta.nCreateTime < nTimeFirstKey)) + nTimeFirstKey = meta.nCreateTime; + + mapKeyMetadata[pubkey.GetID()] = meta; + return true; +} + +bool CWallet::LoadCryptedKey(const CPubKey &vchPubKey, const std::vector &vchCryptedSecret) +{ + return CCryptoKeyStore::AddCryptedKey(vchPubKey, vchCryptedSecret); +} + + +bool CWallet::LoadCryptedSaplingZKey( + const libzcash::SaplingExtendedFullViewingKey &extfvk, + const std::vector &vchCryptedSecret) +{ + return CCryptoKeyStore::AddCryptedSaplingSpendingKey(extfvk, vchCryptedSecret, extfvk.DefaultAddress()); +} + +bool CWallet::LoadSaplingZKeyMetadata(const libzcash::SaplingIncomingViewingKey &ivk, const CKeyMetadata &meta) +{ + AssertLockHeld(cs_wallet); // mapSaplingZKeyMetadata + mapSaplingZKeyMetadata[ivk] = meta; + return true; +} + +bool CWallet::LoadSaplingZKey(const libzcash::SaplingExtendedSpendingKey &key) +{ + return CCryptoKeyStore::AddSaplingSpendingKey(key, key.DefaultAddress()); +} + +bool CWallet::LoadSaplingPaymentAddress( + const libzcash::SaplingPaymentAddress &addr, + const libzcash::SaplingIncomingViewingKey &ivk) +{ + return CCryptoKeyStore::AddSaplingIncomingViewingKey(ivk, addr); +} + +bool CWallet::AddCScript(const CScript& redeemScript) +{ + if (!CCryptoKeyStore::AddCScript(redeemScript)) + return false; + if (!fFileBacked) + return true; + return CWalletDB(strWalletFile).WriteCScript(Hash160(redeemScript), redeemScript); +} + +bool CWallet::LoadCScript(const CScript& redeemScript) +{ + /* A sanity check was added in pull #3843 to avoid adding redeemScripts + * that never can be redeemed. However, old wallets may still contain + * these. Do not add them to the wallet and warn. */ + if (redeemScript.size() > MAX_SCRIPT_ELEMENT_SIZE) + { + std::string strAddr = EncodeDestination(CScriptID(redeemScript)); + LogPrintf("%s: Warning: This wallet contains a redeemScript of size %i which exceeds maximum size %i thus can never be redeemed. Do not use address %s.\n", + __func__, redeemScript.size(), MAX_SCRIPT_ELEMENT_SIZE, strAddr); + return true; + } + + return CCryptoKeyStore::AddCScript(redeemScript); +} + +bool CWallet::AddWatchOnly(const CScript &dest) +{ + if (!CCryptoKeyStore::AddWatchOnly(dest)) + return false; + nTimeFirstKey = 1; // No birthday information for watch-only keys. + NotifyWatchonlyChanged(true); + if (!fFileBacked) + return true; + return CWalletDB(strWalletFile).WriteWatchOnly(dest); +} + +bool CWallet::RemoveWatchOnly(const CScript &dest) +{ + AssertLockHeld(cs_wallet); + if (!CCryptoKeyStore::RemoveWatchOnly(dest)) + return false; + if (!HaveWatchOnly()) + NotifyWatchonlyChanged(false); + if (fFileBacked) + if (!CWalletDB(strWalletFile).EraseWatchOnly(dest)) + return false; + + return true; +} + +bool CWallet::LoadWatchOnly(const CScript &dest) +{ + return CCryptoKeyStore::AddWatchOnly(dest); +} + +// +// Wallet encryption and passphrase management +// + +bool CWallet::Unlock(const SecureString& strWalletPassphrase) +{ + CCrypter crypter; + CKeyingMaterial vMasterKey; + + { + LOCK(cs_wallet); + BOOST_FOREACH(const MasterKeyMap::value_type& pMasterKey, mapMasterKeys) + { + if(!crypter.SetKeyFromPassphrase(strWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod)) + return false; + if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, vMasterKey)) + continue; // try another master key + if (CCryptoKeyStore::Unlock(vMasterKey)) + return true; + } + } + return false; +} + +bool CWallet::ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase, const SecureString& strNewWalletPassphrase) +{ + bool fWasLocked = IsLocked(); + + { + LOCK(cs_wallet); + Lock(); + + CCrypter crypter; + CKeyingMaterial vMasterKey; + BOOST_FOREACH(MasterKeyMap::value_type& pMasterKey, mapMasterKeys) + { + if(!crypter.SetKeyFromPassphrase(strOldWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod)) + return false; + if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, vMasterKey)) + return false; + if (CCryptoKeyStore::Unlock(vMasterKey)) + { + int64_t nStartTime = GetTimeMillis(); + crypter.SetKeyFromPassphrase(strNewWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod); + pMasterKey.second.nDeriveIterations = pMasterKey.second.nDeriveIterations * (100 / ((double)(GetTimeMillis() - nStartTime))); + + nStartTime = GetTimeMillis(); + crypter.SetKeyFromPassphrase(strNewWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod); + pMasterKey.second.nDeriveIterations = (pMasterKey.second.nDeriveIterations + pMasterKey.second.nDeriveIterations * 100 / ((double)(GetTimeMillis() - nStartTime))) / 2; + + if (pMasterKey.second.nDeriveIterations < 25000) + pMasterKey.second.nDeriveIterations = 25000; + + LogPrintf("Wallet passphrase changed to an nDeriveIterations of %i\n", pMasterKey.second.nDeriveIterations); + + if (!crypter.SetKeyFromPassphrase(strNewWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod)) + return false; + if (!crypter.Encrypt(vMasterKey, pMasterKey.second.vchCryptedKey)) + return false; + CWalletDB(strWalletFile).WriteMasterKey(pMasterKey.first, pMasterKey.second); + if (fWasLocked) + Lock(); + return true; + } + } + } + + return false; +} + +bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase) +{ + if (IsCrypted()) + return false; + + CKeyingMaterial vMasterKey; + + vMasterKey.resize(WALLET_CRYPTO_KEY_SIZE); + GetRandBytes(&vMasterKey[0], WALLET_CRYPTO_KEY_SIZE); + + CMasterKey kMasterKey; + + kMasterKey.vchSalt.resize(WALLET_CRYPTO_SALT_SIZE); + GetRandBytes(&kMasterKey.vchSalt[0], WALLET_CRYPTO_SALT_SIZE); + + CCrypter crypter; + int64_t nStartTime = GetTimeMillis(); + crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt, 25000, kMasterKey.nDerivationMethod); + kMasterKey.nDeriveIterations = 2500000 / ((double)(GetTimeMillis() - nStartTime)); + + nStartTime = GetTimeMillis(); + crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt, kMasterKey.nDeriveIterations, kMasterKey.nDerivationMethod); + kMasterKey.nDeriveIterations = (kMasterKey.nDeriveIterations + kMasterKey.nDeriveIterations * 100 / ((double)(GetTimeMillis() - nStartTime))) / 2; + + if (kMasterKey.nDeriveIterations < 25000) + kMasterKey.nDeriveIterations = 25000; + + LogPrintf("Encrypting Wallet with an nDeriveIterations of %i\n", kMasterKey.nDeriveIterations); + + if (!crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt, kMasterKey.nDeriveIterations, kMasterKey.nDerivationMethod)) + return false; + if (!crypter.Encrypt(vMasterKey, kMasterKey.vchCryptedKey)) + return false; + + { + LOCK(cs_wallet); + mapMasterKeys[++nMasterKeyMaxID] = kMasterKey; + if (fFileBacked) + { + assert(!pwalletdbEncryption); + pwalletdbEncryption = new CWalletDB(strWalletFile); + if (!pwalletdbEncryption->TxnBegin()) { + delete pwalletdbEncryption; + pwalletdbEncryption = NULL; + return false; + } + pwalletdbEncryption->WriteMasterKey(nMasterKeyMaxID, kMasterKey); + } + + if (!EncryptKeys(vMasterKey)) + { + if (fFileBacked) { + pwalletdbEncryption->TxnAbort(); + delete pwalletdbEncryption; + } + // We now probably have half of our keys encrypted in memory, and half not... + // die and let the user reload the unencrypted wallet. + assert(false); + } + + // Encryption was introduced in version 0.4.0 + SetMinVersion(FEATURE_WALLETCRYPT, pwalletdbEncryption, true); + + if (fFileBacked) + { + if (!pwalletdbEncryption->TxnCommit()) { + delete pwalletdbEncryption; + // We now have keys encrypted in memory, but not on disk... + // die to avoid confusion and let the user reload the unencrypted wallet. + assert(false); + } + + delete pwalletdbEncryption; + pwalletdbEncryption = NULL; + } + + Lock(); + Unlock(strWalletPassphrase); + NewKeyPool(); + Lock(); + + // Need to completely rewrite the wallet file; if we don't, bdb might keep + // bits of the unencrypted private key in slack space in the database file. + CDB::Rewrite(strWalletFile); + + } + NotifyStatusChanged(this); + + return true; +} + +// +// HD wallet seed management +// + +bool CWallet::IsHDFullyEnabled() const +{ + // Only Sapling addresses are HD for now + return false; +} + +void CWallet::GenerateNewSeed() +{ + LOCK(cs_wallet); + + auto seed = HDSeed::Random(HD_WALLET_SEED_LENGTH); + + int64_t nCreationTime = GetTime(); + + // If the wallet is encrypted and locked, this will fail. + if (!SetHDSeed(seed)) + throw std::runtime_error(std::string(__func__) + ": SetHDSeed failed"); + + // store the key creation time together with + // the child index counter in the database + // as a hdchain object + CHDChain newHdChain; + newHdChain.nVersion = CHDChain::VERSION_HD_BASE; + newHdChain.seedFp = seed.Fingerprint(); + newHdChain.nCreateTime = nCreationTime; + SetHDChain(newHdChain, false); +} + +bool CWallet::SetHDSeed(const HDSeed& seed) +{ + if (!CCryptoKeyStore::SetHDSeed(seed)) { + return false; + } + + if (!fFileBacked) { + return true; + } + + { + LOCK(cs_wallet); + if (!IsCrypted()) { + return CWalletDB(strWalletFile).WriteHDSeed(seed); + } + } + return true; +} + +bool CWallet::SetCryptedHDSeed(const uint256& seedFp, const std::vector &vchCryptedSecret) +{ + if (!CCryptoKeyStore::SetCryptedHDSeed(seedFp, vchCryptedSecret)) { + return false; + } + + if (!fFileBacked) { + return true; + } + + { + LOCK(cs_wallet); + if (pwalletdbEncryption) + return pwalletdbEncryption->WriteCryptedHDSeed(seedFp, vchCryptedSecret); + else + return CWalletDB(strWalletFile).WriteCryptedHDSeed(seedFp, vchCryptedSecret); + } + return false; +} + +void CWallet::SetHDChain(const CHDChain& chain, bool memonly) +{ + LOCK(cs_wallet); + if (!memonly && fFileBacked && !CWalletDB(strWalletFile).WriteHDChain(chain)) + throw std::runtime_error(std::string(__func__) + ": writing chain failed"); + + hdChain = chain; +} + +bool CWallet::LoadHDSeed(const HDSeed& seed) +{ + return CBasicKeyStore::SetHDSeed(seed); +} + +bool CWallet::LoadCryptedHDSeed(const uint256& seedFp, const std::vector& seed) +{ + return CCryptoKeyStore::SetCryptedHDSeed(seedFp, seed); +} + +// +// Key pool management +// + +bool CWallet::NewKeyPool() +{ + { + LOCK(cs_wallet); + CWalletDB walletdb(strWalletFile); + BOOST_FOREACH(int64_t nIndex, setKeyPool) + walletdb.ErasePool(nIndex); + setKeyPool.clear(); + + if (IsLocked()) + return false; + + int64_t nKeys = max(GetArg("-keypool", 100), (int64_t)0); + for (int i = 0; i < nKeys; i++) + { + int64_t nIndex = i+1; + walletdb.WritePool(nIndex, CKeyPool(GenerateNewKey())); + setKeyPool.insert(nIndex); + } + LogPrintf("CWallet::NewKeyPool wrote %d new keys\n", nKeys); + } + return true; +} + +bool CWallet::TopUpKeyPool(unsigned int kpSize) +{ + { + LOCK(cs_wallet); + + if (IsLocked()) + return false; + + CWalletDB walletdb(strWalletFile); + + // Top up key pool + unsigned int nTargetSize; + if (kpSize > 0) + nTargetSize = kpSize; + else + nTargetSize = max(GetArg("-keypool", 100), (int64_t) 0); + + while (setKeyPool.size() < (nTargetSize + 1)) + { + int64_t nEnd = 1; + if (!setKeyPool.empty()) + nEnd = *(--setKeyPool.end()) + 1; + if (!walletdb.WritePool(nEnd, CKeyPool(GenerateNewKey()))) + throw runtime_error("TopUpKeyPool(): writing generated key failed"); + setKeyPool.insert(nEnd); + LogPrintf("keypool added key %d, size=%u\n", nEnd, setKeyPool.size()); + } + } + return true; +} + +void CWallet::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool) +{ + nIndex = -1; + keypool.vchPubKey = CPubKey(); + { + LOCK(cs_wallet); + + if (!IsLocked()) + TopUpKeyPool(); + + // Get the oldest key + if(setKeyPool.empty()) + return; + + CWalletDB walletdb(strWalletFile); + + nIndex = *(setKeyPool.begin()); + setKeyPool.erase(setKeyPool.begin()); + if (!walletdb.ReadPool(nIndex, keypool)) + throw runtime_error("ReserveKeyFromKeyPool(): read failed"); + if (!HaveKey(keypool.vchPubKey.GetID())) + throw runtime_error("ReserveKeyFromKeyPool(): unknown key in key pool"); + assert(keypool.vchPubKey.IsValid()); + //LogPrintf("keypool reserve %d\n", nIndex); + } +} + +void CWallet::KeepKey(int64_t nIndex) +{ + // Remove from key pool + if (fFileBacked) + { + CWalletDB walletdb(strWalletFile); + walletdb.ErasePool(nIndex); + } + LogPrintf("keypool keep %d\n", nIndex); +} + +void CWallet::ReturnKey(int64_t nIndex) +{ + // Return to key pool + { + LOCK(cs_wallet); + setKeyPool.insert(nIndex); + } + //LogPrintf("keypool return %d\n", nIndex); +} + +bool CWallet::GetKeyFromPool(CPubKey& result) +{ + int64_t nIndex = 0; + CKeyPool keypool; + { + LOCK(cs_wallet); + ReserveKeyFromKeyPool(nIndex, keypool); + if (nIndex == -1) + { + if (IsLocked()) return false; + result = GenerateNewKey(); + return true; + } + KeepKey(nIndex); + result = keypool.vchPubKey; + } + return true; +} + +int64_t CWallet::GetOldestKeyPoolTime() +{ + int64_t nIndex = 0; + CKeyPool keypool; + ReserveKeyFromKeyPool(nIndex, keypool); + if (nIndex == -1) + return GetTime(); + ReturnKey(nIndex); + return keypool.nTime; +} + +// +// CReserveKey +// + +bool CReserveKey::GetReservedKey(CPubKey& pubkey) +{ + if (nIndex == -1) + { + CKeyPool keypool; + pwallet->ReserveKeyFromKeyPool(nIndex, keypool); + if (nIndex != -1) + vchPubKey = keypool.vchPubKey; + else { + return false; + } + } + assert(vchPubKey.IsValid()); + pubkey = vchPubKey; + return true; +} + +void CReserveKey::KeepKey() +{ + if (nIndex != -1) + pwallet->KeepKey(nIndex); + nIndex = -1; + vchPubKey = CPubKey(); +} + +void CReserveKey::ReturnKey() +{ + if (nIndex != -1) + pwallet->ReturnKey(nIndex); + nIndex = -1; + vchPubKey = CPubKey(); +} + +void CWallet::GetAllReserveKeys(set& setAddress) const +{ + setAddress.clear(); + + CWalletDB walletdb(strWalletFile); + + LOCK2(cs_main, cs_wallet); + BOOST_FOREACH(const int64_t& id, setKeyPool) + { + CKeyPool keypool; + if (!walletdb.ReadPool(id, keypool)) + throw runtime_error("GetAllReserveKeyHashes(): read failed"); + assert(keypool.vchPubKey.IsValid()); + CKeyID keyID = keypool.vchPubKey.GetID(); + if (!HaveKey(keyID)) + throw runtime_error("GetAllReserveKeyHashes(): unknown key in key pool"); + setAddress.insert(keyID); + } +} + +// +// Key birth times +// + +class CAffectedKeysVisitor : public boost::static_visitor { +private: + const CKeyStore &keystore; + std::vector &vKeys; + +public: + CAffectedKeysVisitor(const CKeyStore &keystoreIn, std::vector &vKeysIn) : keystore(keystoreIn), vKeys(vKeysIn) {} + + void Process(const CScript &script) { + txnouttype type; + std::vector vDest; + int nRequired; + if (ExtractDestinations(script, type, vDest, nRequired)) { + BOOST_FOREACH(const CTxDestination &dest, vDest) + boost::apply_visitor(*this, dest); + } + } + + void operator()(const CKeyID &keyId) { + if (keystore.HaveKey(keyId)) + vKeys.push_back(keyId); + } + + void operator()(const CPubKey &key) { + CKeyID keyId = key.GetID(); + if (keystore.HaveKey(keyId)) + vKeys.push_back(keyId); + } + + void operator()(const CScriptID &scriptId) { + CScript script; + if (keystore.GetCScript(scriptId, script)) + Process(script); + } + + void operator()(const CNoDestination &none) {} +}; + +void CWallet::GetKeyBirthTimes(std::map &mapKeyBirth) const { + AssertLockHeld(cs_wallet); // mapKeyMetadata + mapKeyBirth.clear(); + + // get birth times for keys with metadata + for (std::map::const_iterator it = mapKeyMetadata.begin(); it != mapKeyMetadata.end(); it++) + if (it->second.nCreateTime) + mapKeyBirth[it->first] = it->second.nCreateTime; + + // map in which we'll infer heights of other keys + CBlockIndex *pindexMax = chainActive[std::max(0, chainActive.Height() - 144)]; // the tip can be reorganised; use a 144-block safety margin + std::map mapKeyFirstBlock; + std::set setKeys; + GetKeys(setKeys); + BOOST_FOREACH(const CKeyID &keyid, setKeys) { + if (mapKeyBirth.count(keyid) == 0) + mapKeyFirstBlock[keyid] = pindexMax; + } + setKeys.clear(); + + // if there are no such keys, we're done + if (mapKeyFirstBlock.empty()) + return; + + // find first block that affects those keys, if there are any left + std::vector vAffected; + for (std::map::const_iterator it = mapWallet.begin(); it != mapWallet.end(); it++) { + // iterate over all wallet transactions... + const CWalletTx &wtx = (*it).second; + BlockMap::const_iterator blit = mapBlockIndex.find(wtx.hashBlock); + if (blit != mapBlockIndex.end() && chainActive.Contains(blit->second)) { + // ... which are already in a block + int nHeight = blit->second->GetHeight(); + BOOST_FOREACH(const CTxOut &txout, wtx.vout) { + // iterate over all their outputs + CAffectedKeysVisitor(*this, vAffected).Process(txout.scriptPubKey); + BOOST_FOREACH(const CKeyID &keyid, vAffected) { + // ... and all their affected keys + std::map::iterator rit = mapKeyFirstBlock.find(keyid); + if (rit != mapKeyFirstBlock.end() && nHeight < rit->second->GetHeight()) + rit->second = blit->second; + } + vAffected.clear(); + } + } + } + + // Extract block timestamps for those keys + for (std::map::const_iterator it = mapKeyFirstBlock.begin(); it != mapKeyFirstBlock.end(); it++) + mapKeyBirth[it->first] = it->second->GetBlockTime() - 7200; // block times can be 2h off +} + +// +// Shielded key and address generalizations +// + +bool IncomingViewingKeyBelongsToWallet::operator()(const libzcash::SaplingPaymentAddress &zaddr) const +{ + libzcash::SaplingIncomingViewingKey ivk; + return m_wallet->GetSaplingIncomingViewingKey(zaddr, ivk); +} + +bool IncomingViewingKeyBelongsToWallet::operator()(const libzcash::InvalidEncoding& no) const +{ + return false; +} + +bool PaymentAddressBelongsToWallet::operator()(const libzcash::SaplingPaymentAddress &zaddr) const +{ + libzcash::SaplingIncomingViewingKey ivk; + + // If we have a SaplingExtendedSpendingKey in the wallet, then we will + // also have the corresponding SaplingFullViewingKey. + return m_wallet->GetSaplingIncomingViewingKey(zaddr, ivk) && + m_wallet->HaveSaplingFullViewingKey(ivk); +} + +bool PaymentAddressBelongsToWallet::operator()(const libzcash::InvalidEncoding& no) const +{ + return false; +} + +bool HaveSpendingKeyForPaymentAddress::operator()(const libzcash::SaplingPaymentAddress &zaddr) const +{ + libzcash::SaplingIncomingViewingKey ivk; + libzcash::SaplingFullViewingKey fvk; + + return m_wallet->GetSaplingIncomingViewingKey(zaddr, ivk) && + m_wallet->GetSaplingFullViewingKey(ivk, fvk) && + m_wallet->HaveSaplingSpendingKey(fvk); +} + +bool HaveSpendingKeyForPaymentAddress::operator()(const libzcash::InvalidEncoding& no) const +{ + return false; +} + +boost::optional GetSpendingKeyForPaymentAddress::operator()( + const libzcash::SaplingPaymentAddress &zaddr) const +{ + libzcash::SaplingExtendedSpendingKey extsk; + if (m_wallet->GetSaplingExtendedSpendingKey(zaddr, extsk)) { + return libzcash::SpendingKey(extsk); + } else { + return boost::none; + } +} + +boost::optional GetSpendingKeyForPaymentAddress::operator()( + const libzcash::InvalidEncoding& no) const +{ + // Defaults to InvalidEncoding + return libzcash::SpendingKey(); +} + + +SpendingKeyAddResult AddSpendingKeyToWallet::operator()(const libzcash::SaplingExtendedSpendingKey &sk) const { + auto fvk = sk.expsk.full_viewing_key(); + auto ivk = fvk.in_viewing_key(); + auto addr = sk.DefaultAddress(); + { + if (log){ + LogPrint("zrpc", "Importing zaddr %s...\n", EncodePaymentAddress(addr)); + } + // Don't throw error in case a key is already there + if (m_wallet->HaveSaplingSpendingKey(fvk)) { + return KeyAlreadyExists; + } else { + if (!m_wallet-> AddSaplingZKey(sk, addr)) { + return KeyNotAdded; + } + + // Sapling addresses can't have been used in transactions prior to activation. + if (params.vUpgrades[Consensus::UPGRADE_SAPLING].nActivationHeight == Consensus::NetworkUpgrade::ALWAYS_ACTIVE) { + m_wallet->mapSaplingZKeyMetadata[ivk].nCreateTime = nTime; + } else { + // TODO: set a better time for HUSH+DRAGONX + // 154051200 seconds from epoch is Friday, 26 October 2018 00:00:00 GMT - definitely before Sapling activates + m_wallet->mapSaplingZKeyMetadata[ivk].nCreateTime = std::max((int64_t) 154051200, nTime); + } + if (hdKeypath) { + m_wallet->mapSaplingZKeyMetadata[ivk].hdKeypath = hdKeypath.get(); + } + if (seedFpStr) { + uint256 seedFp; + seedFp.SetHex(seedFpStr.get()); + m_wallet->mapSaplingZKeyMetadata[ivk].seedFp = seedFp; + } + return KeyAdded; + } + } +} + +SpendingKeyAddResult AddSpendingKeyToWallet::operator()(const libzcash::InvalidEncoding& no) const { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid spending key"); +}