mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-06-09 06:09:48 +02:00
Merge #13756: wallet: "avoid_reuse" wallet flag for improved privacy
5ebc6b0eb2bitcoind: update -avoidpartialspends description to account for auto-enable for avoid_reuse wallets (Karl-Johan Alm)ada258f8c8doc: release notes for avoid_reuse (Karl-Johan Alm)27669551dawallet: enable avoid_partial_spends by default if avoid_reuse is set (Karl-Johan Alm)8f2e208f7ctest: add test for avoidreuse feature (Karl-Johan Alm)0bdfbd34cfwallet/rpc: add 'avoid_reuse' option to RPC commands (Karl-Johan Alm)f904723e0dwallet/rpc: add setwalletflag RPC and MUTABLE_WALLET_FLAGS (Karl-Johan Alm)8247a0da3awallet: enable avoid_reuse feature (Karl-Johan Alm)eec15662fawallet: avoid reuse flags (Karl-Johan Alm)58928098c2wallet: make IsWalletFlagSet() const (Karl-Johan Alm)129a5bafd9wallet: rename g_known_wallet_flags constant to KNOWN_WALLET_FLAGS (Karl-Johan Alm) Pull request description: Add a new wallet flag called `avoid_reuse` which, when enabled, will keep track of when a specific destination has been spent from, and will actively "blacklist" any new UTXOs which send to an already-spent-from destination. This improves privacy, as a payer could otherwise begin tracking a payee's wallet by regularly peppering a known UTXO with dust outputs, which would then be scooped up and used in payments by the payee, allowing the payer to map out (1) the inputs owned by the payee and (2) the destinations to which the payee is making payments. This replaces #10386 and together with the (now merged) #12257 it addresses #10065 in full. The concerns raised in https://github.com/bitcoin/bitcoin/pull/10386#issuecomment-302361381 are also addressed due to #12257. ~~Note: this builds on top of #15780.~~ (merged) ACKs for commit 5ebc6b: jnewbery: ACK5ebc6b0eblaanwj: Concept and code-review ACK5ebc6b0eb2meshcollider: Code review ACK5ebc6b0eb2achow101: ACK5ebc6b0eb2modulo above nits Tree-SHA512: fdef45826af544cbbb45634ac367852cc467ec87081d86d08b53ca849e588617e9a0a255b7e7bb28692d15332de58d6c3d274ac003355220e4213d7d9070742e
This commit is contained in:
@@ -36,6 +36,14 @@
|
||||
|
||||
#include <boost/algorithm/string/replace.hpp>
|
||||
|
||||
const std::map<uint64_t,std::string> WALLET_FLAG_CAVEATS{
|
||||
{WALLET_FLAG_AVOID_REUSE,
|
||||
"You need to rescan the blockchain in order to correctly mark used "
|
||||
"destinations in the past. Until this is done, some destinations may "
|
||||
"be considered unused, even if the opposite is the case."
|
||||
},
|
||||
};
|
||||
|
||||
static const size_t OUTPUT_GROUP_MAX_ENTRIES = 10;
|
||||
|
||||
static CCriticalSection cs_wallets;
|
||||
@@ -932,6 +940,37 @@ bool CWallet::MarkReplaced(const uint256& originalHash, const uint256& newHash)
|
||||
return success;
|
||||
}
|
||||
|
||||
void CWallet::SetUsedDestinationState(const uint256& hash, unsigned int n, bool used)
|
||||
{
|
||||
const CWalletTx* srctx = GetWalletTx(hash);
|
||||
if (!srctx) return;
|
||||
|
||||
CTxDestination dst;
|
||||
if (ExtractDestination(srctx->tx->vout[n].scriptPubKey, dst)) {
|
||||
if (::IsMine(*this, dst)) {
|
||||
LOCK(cs_wallet);
|
||||
if (used && !GetDestData(dst, "used", nullptr)) {
|
||||
AddDestData(dst, "used", "p"); // p for "present", opposite of absent (null)
|
||||
} else if (!used && GetDestData(dst, "used", nullptr)) {
|
||||
EraseDestData(dst, "used");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool CWallet::IsUsedDestination(const CTxDestination& dst) const
|
||||
{
|
||||
LOCK(cs_wallet);
|
||||
return ::IsMine(*this, dst) && GetDestData(dst, "used", nullptr);
|
||||
}
|
||||
|
||||
bool CWallet::IsUsedDestination(const uint256& hash, unsigned int n) const
|
||||
{
|
||||
CTxDestination dst;
|
||||
const CWalletTx* srctx = GetWalletTx(hash);
|
||||
return srctx && ExtractDestination(srctx->tx->vout[n].scriptPubKey, dst) && IsUsedDestination(dst);
|
||||
}
|
||||
|
||||
bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose)
|
||||
{
|
||||
LOCK(cs_wallet);
|
||||
@@ -940,6 +979,14 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose)
|
||||
|
||||
uint256 hash = wtxIn.GetHash();
|
||||
|
||||
if (IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE)) {
|
||||
// Mark used destinations
|
||||
for (const CTxIn& txin : wtxIn.tx->vin) {
|
||||
const COutPoint& op = txin.prevout;
|
||||
SetUsedDestinationState(op.hash, op.n, true);
|
||||
}
|
||||
}
|
||||
|
||||
// Inserts only if not already there, returns tx inserted or tx found
|
||||
std::pair<std::map<uint256, CWalletTx>::iterator, bool> ret = mapWallet.insert(std::make_pair(hash, wtxIn));
|
||||
CWalletTx& wtx = (*ret.first).second;
|
||||
@@ -1557,7 +1604,7 @@ void CWallet::UnsetWalletFlagWithDB(WalletBatch& batch, uint64_t flag)
|
||||
throw std::runtime_error(std::string(__func__) + ": writing wallet flags failed");
|
||||
}
|
||||
|
||||
bool CWallet::IsWalletFlagSet(uint64_t flag)
|
||||
bool CWallet::IsWalletFlagSet(uint64_t flag) const
|
||||
{
|
||||
return (m_wallet_flags & flag);
|
||||
}
|
||||
@@ -1566,7 +1613,7 @@ bool CWallet::SetWalletFlags(uint64_t overwriteFlags, bool memonly)
|
||||
{
|
||||
LOCK(cs_wallet);
|
||||
m_wallet_flags = overwriteFlags;
|
||||
if (((overwriteFlags & g_known_wallet_flags) >> 32) ^ (overwriteFlags >> 32)) {
|
||||
if (((overwriteFlags & KNOWN_WALLET_FLAGS) >> 32) ^ (overwriteFlags >> 32)) {
|
||||
// contains unknown non-tolerable wallet flags
|
||||
return false;
|
||||
}
|
||||
@@ -2059,7 +2106,7 @@ CAmount CWalletTx::GetAvailableCredit(interfaces::Chain::Lock& locked_chain, boo
|
||||
return 0;
|
||||
|
||||
// Avoid caching ismine for NO or ALL cases (could remove this check and simplify in the future).
|
||||
bool allow_cache = filter == ISMINE_SPENDABLE || filter == ISMINE_WATCH_ONLY;
|
||||
bool allow_cache = (filter & ISMINE_ALL) && (filter & ISMINE_ALL) != ISMINE_ALL;
|
||||
|
||||
// Must wait until coinbase is safely deep enough in the chain before valuing it
|
||||
if (IsImmatureCoinBase(locked_chain))
|
||||
@@ -2069,12 +2116,12 @@ CAmount CWalletTx::GetAvailableCredit(interfaces::Chain::Lock& locked_chain, boo
|
||||
return m_amounts[AVAILABLE_CREDIT].m_value[filter];
|
||||
}
|
||||
|
||||
bool allow_used_addresses = (filter & ISMINE_USED) || !pwallet->IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE);
|
||||
CAmount nCredit = 0;
|
||||
uint256 hashTx = GetHash();
|
||||
for (unsigned int i = 0; i < tx->vout.size(); i++)
|
||||
{
|
||||
if (!pwallet->IsSpent(locked_chain, hashTx, i))
|
||||
{
|
||||
if (!pwallet->IsSpent(locked_chain, hashTx, i) && (allow_used_addresses || !pwallet->IsUsedDestination(hashTx, i))) {
|
||||
const CTxOut &txout = tx->vout[i];
|
||||
nCredit += pwallet->GetCredit(txout, filter);
|
||||
if (!MoneyRange(nCredit))
|
||||
@@ -2216,9 +2263,10 @@ void MaybeResendWalletTxs()
|
||||
*/
|
||||
|
||||
|
||||
CWallet::Balance CWallet::GetBalance(const int min_depth) const
|
||||
CWallet::Balance CWallet::GetBalance(const int min_depth, bool avoid_reuse) const
|
||||
{
|
||||
Balance ret;
|
||||
isminefilter reuse_filter = avoid_reuse ? 0 : ISMINE_USED;
|
||||
{
|
||||
auto locked_chain = chain().lock();
|
||||
LOCK(cs_wallet);
|
||||
@@ -2227,8 +2275,8 @@ CWallet::Balance CWallet::GetBalance(const int min_depth) const
|
||||
const CWalletTx& wtx = entry.second;
|
||||
const bool is_trusted{wtx.IsTrusted(*locked_chain)};
|
||||
const int tx_depth{wtx.GetDepthInMainChain(*locked_chain)};
|
||||
const CAmount tx_credit_mine{wtx.GetAvailableCredit(*locked_chain, /* fUseCache */ true, ISMINE_SPENDABLE)};
|
||||
const CAmount tx_credit_watchonly{wtx.GetAvailableCredit(*locked_chain, /* fUseCache */ true, ISMINE_WATCH_ONLY)};
|
||||
const CAmount tx_credit_mine{wtx.GetAvailableCredit(*locked_chain, /* fUseCache */ true, ISMINE_SPENDABLE | reuse_filter)};
|
||||
const CAmount tx_credit_watchonly{wtx.GetAvailableCredit(*locked_chain, /* fUseCache */ true, ISMINE_WATCH_ONLY | reuse_filter)};
|
||||
if (is_trusted && tx_depth >= min_depth) {
|
||||
ret.m_mine_trusted += tx_credit_mine;
|
||||
ret.m_watchonly_trusted += tx_credit_watchonly;
|
||||
@@ -2266,6 +2314,9 @@ void CWallet::AvailableCoins(interfaces::Chain::Lock& locked_chain, std::vector<
|
||||
|
||||
vCoins.clear();
|
||||
CAmount nTotal = 0;
|
||||
// Either the WALLET_FLAG_AVOID_REUSE flag is not set (in which case we always allow), or we default to avoiding, and only in the case where
|
||||
// a coin control object is provided, and has the avoid address reuse flag set to false, do we allow already used addresses
|
||||
bool allow_used_addresses = !IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE) || (coinControl && !coinControl->m_avoid_address_reuse);
|
||||
|
||||
for (const auto& entry : mapWallet)
|
||||
{
|
||||
@@ -2347,6 +2398,10 @@ void CWallet::AvailableCoins(interfaces::Chain::Lock& locked_chain, std::vector<
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!allow_used_addresses && IsUsedDestination(wtxid, i)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
bool solvable = IsSolvable(*this, wtx.tx->vout[i].scriptPubKey);
|
||||
bool spendable = ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || (((mine & ISMINE_WATCH_ONLY) != ISMINE_NO) && (coinControl && coinControl->fAllowWatchOnly && solvable));
|
||||
|
||||
@@ -4137,16 +4192,12 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain,
|
||||
// ensure this wallet.dat can only be opened by clients supporting HD with chain split and expects no default key
|
||||
walletInstance->SetMinVersion(FEATURE_LATEST);
|
||||
|
||||
if ((wallet_creation_flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
|
||||
//selective allow to set flags
|
||||
walletInstance->SetWalletFlag(WALLET_FLAG_DISABLE_PRIVATE_KEYS);
|
||||
} else if (wallet_creation_flags & WALLET_FLAG_BLANK_WALLET) {
|
||||
walletInstance->SetWalletFlag(WALLET_FLAG_BLANK_WALLET);
|
||||
} else {
|
||||
walletInstance->SetWalletFlags(wallet_creation_flags, false);
|
||||
if (!(wallet_creation_flags & (WALLET_FLAG_DISABLE_PRIVATE_KEYS | WALLET_FLAG_BLANK_WALLET))) {
|
||||
// generate a new seed
|
||||
CPubKey seed = walletInstance->GenerateNewSeed();
|
||||
walletInstance->SetHDSeed(seed);
|
||||
} // Otherwise, do not generate a new seed
|
||||
}
|
||||
|
||||
// Top up the keypool
|
||||
if (walletInstance->CanGenerateKeys() && !walletInstance->TopUpKeyPool()) {
|
||||
|
||||
Reference in New Issue
Block a user