Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/privatesend/privatesend-client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1254,7 +1254,7 @@ bool CPrivateSendClientSession::SubmitDenominate(CConnman& connman)

std::vector<std::pair<int, size_t> > vecInputsByRounds;

for (int i = 0; i < CPrivateSendClientOptions::GetRounds(); i++) {
for (int i = 0; i < CPrivateSendClientOptions::GetRounds() + CPrivateSendClientOptions::GetRandomRounds(); i++) {
if (PrepareDenominate(i, i, strError, vecPSInOutPairs, vecPSInOutPairsTmp, true)) {
LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::SubmitDenominate -- Running PrivateSend denominate for %d rounds, success\n", i);
vecInputsByRounds.emplace_back(i, vecPSInOutPairsTmp.size());
Expand Down
9 changes: 7 additions & 2 deletions src/privatesend/privatesend-client.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ static const int PRIVATESEND_DENOM_OUTPUTS_THRESHOLD = 500;
static const int PRIVATESEND_KEYS_THRESHOLD_WARNING = 100;
// Stop mixing completely, it's too dangerous to continue when we have only this many keys left
static const int PRIVATESEND_KEYS_THRESHOLD_STOP = 50;
// Pseudorandomly mix up to this many times in addition to base round count
static const int PRIVATESEND_RANDOM_ROUNDS = 3;

// The main object for accessing mixing
extern std::map<const std::string, CPrivateSendClientManager*> privateSendClientManagers;
Expand Down Expand Up @@ -224,8 +226,8 @@ class CPrivateSendClientManager
bool CheckAutomaticBackup();

public:
int nCachedNumBlocks; //used for the overview screen
bool fCreateAutoBackups; //builtin support for automatic backups
int nCachedNumBlocks; // used for the overview screen
bool fCreateAutoBackups; // builtin support for automatic backups

CPrivateSendClientManager() :
vecMasternodesUsed(),
Expand Down Expand Up @@ -280,6 +282,7 @@ class CPrivateSendClientOptions
public:
static int GetSessions() { return CPrivateSendClientOptions::Get().nPrivateSendSessions; }
static int GetRounds() { return CPrivateSendClientOptions::Get().nPrivateSendRounds; }
static int GetRandomRounds() { return CPrivateSendClientOptions::Get().nPrivateSendRandomRounds; }
static int GetAmount() { return CPrivateSendClientOptions::Get().nPrivateSendAmount; }
static int GetDenomsGoal() { return CPrivateSendClientOptions::Get().nPrivateSendDenomsGoal; }
static int GetDenomsHardCap() { return CPrivateSendClientOptions::Get().nPrivateSendDenomsHardCap; }
Expand All @@ -301,6 +304,7 @@ class CPrivateSendClientOptions
CCriticalSection cs_ps_options;
int nPrivateSendSessions;
int nPrivateSendRounds;
int nPrivateSendRandomRounds;
int nPrivateSendAmount;
int nPrivateSendDenomsGoal;
int nPrivateSendDenomsHardCap;
Expand All @@ -309,6 +313,7 @@ class CPrivateSendClientOptions

CPrivateSendClientOptions() :
nPrivateSendRounds(DEFAULT_PRIVATESEND_ROUNDS),
nPrivateSendRandomRounds(PRIVATESEND_RANDOM_ROUNDS),
nPrivateSendAmount(DEFAULT_PRIVATESEND_AMOUNT),
nPrivateSendDenomsGoal(DEFAULT_PRIVATESEND_DENOMS_GOAL),
nPrivateSendDenomsHardCap(DEFAULT_PRIVATESEND_DENOMS_HARDCAP),
Expand Down
4 changes: 2 additions & 2 deletions src/qt/coincontroldialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -706,9 +706,8 @@ void CoinControlDialog::updateView()
int nChildren = 0;
for (const COutput& out : coins.second) {
COutPoint outpoint = COutPoint(out.tx->tx->GetHash(), out.i);
int nRounds = model->getRealOutpointPrivateSendRounds(outpoint);

if ((coinControl()->IsUsingPrivateSend() && nRounds >= CPrivateSendClientOptions::GetRounds()) || !(coinControl()->IsUsingPrivateSend())) {
if ((coinControl()->IsUsingPrivateSend() && model->isFullyMixed(outpoint)) || !(coinControl()->IsUsingPrivateSend())) {
nSum += out.tx->tx->vout[out.i].nValue;
nChildren++;

Expand Down Expand Up @@ -759,6 +758,7 @@ void CoinControlDialog::updateView()
itemOutput->setData(COLUMN_DATE, Qt::UserRole, QVariant((qlonglong)out.tx->GetTxTime()));

// PrivateSend rounds
int nRounds = model->getRealOutpointPrivateSendRounds(outpoint);
if (nRounds >= 0 || LogAcceptCategory(BCLog::PRIVATESEND)) {
itemOutput->setText(COLUMN_PRIVATESEND_ROUNDS, QString::number(nRounds));
} else {
Expand Down
5 changes: 5 additions & 0 deletions src/qt/walletmodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,11 @@ int WalletModel::getRealOutpointPrivateSendRounds(const COutPoint& outpoint) con
return wallet->GetRealOutpointPrivateSendRounds(outpoint);
}

bool WalletModel::isFullyMixed(const COutPoint& outpoint) const
{
return wallet->IsFullyMixed(outpoint);
}

void WalletModel::updateAddressBook(const QString &address, const QString &label,
bool isMine, const QString &purpose, int status)
{
Expand Down
1 change: 1 addition & 0 deletions src/qt/walletmodel.h
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ class WalletModel : public QObject
int getNumISLocks() const;

int getRealOutpointPrivateSendRounds(const COutPoint& outpoint) const;
bool isFullyMixed(const COutPoint& outpoint) const;

QString getWalletName() const;

Expand Down
2 changes: 1 addition & 1 deletion src/wallet/rpcwallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3167,7 +3167,7 @@ UniValue listunspent(const JSONRPCRequest& request)
entry.pushKV("spendable", out.fSpendable);
entry.pushKV("solvable", out.fSolvable);
entry.pushKV("safe", out.fSafe);
entry.pushKV("ps_rounds", pwallet->GetCappedOutpointPrivateSendRounds(COutPoint(out.tx->GetHash(), out.i)));
entry.pushKV("ps_rounds", pwallet->GetRealOutpointPrivateSendRounds(COutPoint(out.tx->GetHash(), out.i)));
results.push_back(entry);
}

Expand Down
64 changes: 51 additions & 13 deletions src/wallet/wallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1583,9 +1583,11 @@ int CWallet::GetRealOutpointPrivateSendRounds(const COutPoint& outpoint, int nRo
{
LOCK(cs_wallet);

if (nRounds >= MAX_PRIVATESEND_ROUNDS) {
// there can only be MAX_PRIVATESEND_ROUNDS rounds max
return MAX_PRIVATESEND_ROUNDS - 1;
const int nRoundsMax = MAX_PRIVATESEND_ROUNDS + CPrivateSendClientOptions::GetRandomRounds();

if (nRounds >= nRoundsMax) {
// there can only be nRoundsMax rounds max
return nRoundsMax - 1;
}

auto pair = mapOutpointRoundsCache.emplace(outpoint, -10);
Expand Down Expand Up @@ -1651,7 +1653,7 @@ int CWallet::GetRealOutpointPrivateSendRounds(const COutPoint& outpoint, int nRo
}
}
*nRoundsRef = fDenomFound
? (nShortest >= MAX_PRIVATESEND_ROUNDS - 1 ? MAX_PRIVATESEND_ROUNDS : nShortest + 1) // good, we a +1 to the shortest one but only MAX_PRIVATESEND_ROUNDS rounds max allowed
? (nShortest >= nRoundsMax - 1 ? nRoundsMax : nShortest + 1) // good, we a +1 to the shortest one but only nRoundsMax rounds max allowed
: 0; // too bad, we are the fist one in that chain
LogPrint(BCLog::PRIVATESEND, "%s UPDATED %-70s %3d\n", __func__, outpoint.ToStringShort(), *nRoundsRef);
return *nRoundsRef;
Expand Down Expand Up @@ -1681,6 +1683,29 @@ bool CWallet::IsDenominated(const COutPoint& outpoint) const
return CPrivateSend::IsDenominatedAmount(it->second.tx->vout[outpoint.n].nValue);
}

bool CWallet::IsFullyMixed(const COutPoint& outpoint) const
{
int nRounds = GetRealOutpointPrivateSendRounds(outpoint);
// Mix again if we don't have N rounds yet
if (nRounds < CPrivateSendClientOptions::GetRounds()) return false;

// Try to mix a "random" number of rounds more than minimum.
// If we have already mixed N + MaxOffset rounds, don't mix again.
// Otherwise, we should mix again 50% of the time, this results in an exponential decay
// N rounds 50% N+1 25% N+2 12.5%... until we reach N + GetRandomRounds() rounds where we stop.
if (nRounds < CPrivateSendClientOptions::GetRounds() + CPrivateSendClientOptions::GetRandomRounds()) {
CDataStream ss(SER_GETHASH, PROTOCOL_VERSION);
ss << outpoint << nPrivateSendSalt;
uint256 nHash;
CSHA256().Write((const unsigned char*)ss.data(), ss.size()).Finalize(nHash.begin());
if (nHash.GetCheapHash() % 2 == 0) {
return false;
}
}

return true;
}

isminetype CWallet::IsMine(const CTxOut& txout) const
{
return ::IsMine(*this, txout.scriptPubKey);
Expand Down Expand Up @@ -2363,8 +2388,7 @@ CAmount CWalletTx::GetAnonymizedCredit(bool fUseCache) const

if (pwallet->IsSpent(hashTx, i) || !CPrivateSend::IsDenominatedAmount(txout.nValue)) continue;

const int nRounds = pwallet->GetCappedOutpointPrivateSendRounds(outpoint);
if (nRounds >= CPrivateSendClientOptions::GetRounds()) {
if (pwallet->IsFullyMixed(outpoint)) {
nCredit += pwallet->GetCredit(txout, ISMINE_SPENDABLE);
if (!MoneyRange(nCredit))
throw std::runtime_error(std::string(__func__) + ": value out of range");
Expand Down Expand Up @@ -2830,12 +2854,10 @@ void CWallet::AvailableCoins(std::vector<COutput> &vCoins, bool fOnlySafe, const
bool found = false;
if (nCoinType == CoinType::ONLY_FULLY_MIXED) {
if (!CPrivateSend::IsDenominatedAmount(pcoin->tx->vout[i].nValue)) continue;
int nRounds = GetCappedOutpointPrivateSendRounds(COutPoint(wtxid, i));
found = nRounds >= CPrivateSendClientOptions::GetRounds();
found = IsFullyMixed(COutPoint(wtxid, i));
} else if(nCoinType == CoinType::ONLY_READY_TO_MIX) {
if (!CPrivateSend::IsDenominatedAmount(pcoin->tx->vout[i].nValue)) continue;
int nRounds = GetCappedOutpointPrivateSendRounds(COutPoint(wtxid, i));
found = nRounds < CPrivateSendClientOptions::GetRounds();
found = !IsFullyMixed(COutPoint(wtxid, i));
} else if(nCoinType == CoinType::ONLY_NONDENOMINATED) {
if (CPrivateSend::IsCollateralAmount(pcoin->tx->vout[i].nValue)) continue; // do not use collateral amounts
found = !CPrivateSend::IsDenominatedAmount(pcoin->tx->vout[i].nValue);
Expand Down Expand Up @@ -2951,6 +2973,21 @@ const CTxOut& CWallet::FindNonChangeParentOutput(const CTransaction& tx, int out
return ptx->vout[n];
}

void CWallet::InitPrivateSendSalt()
{
// Avoid fetching it multiple times
assert(nPrivateSendSalt.IsNull());

WalletBatch batch(*database);
batch.ReadPrivateSendSalt(nPrivateSendSalt);

while (nPrivateSendSalt.IsNull()) {
// We never generated/saved it
nPrivateSendSalt = GetRandHash();
batch.WritePrivateSendSalt(nPrivateSendSalt);
}
}

static void ApproximateBestSubset(const std::vector<CInputCoin>& vValue, const CAmount& nTotalLower, const CAmount& nTargetValue,
std::vector<char>& vfBest, CAmount& nBest, int iterations = 1000)
{
Expand Down Expand Up @@ -3216,8 +3253,7 @@ bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAm
if (nCoinType == CoinType::ONLY_FULLY_MIXED) {
// Make sure to include mixed preset inputs only,
// even if some non-mixed inputs were manually selected via CoinControl
int nRounds = GetRealOutpointPrivateSendRounds(outpoint);
if (nRounds < CPrivateSendClientOptions::GetRounds()) continue;
if (!IsFullyMixed(outpoint)) continue;
}
nValueFromPresetInputs += pcoin->tx->vout[outpoint.n].nValue;
setPresetCoins.insert(CInputCoin(pcoin, outpoint.n));
Expand Down Expand Up @@ -3420,7 +3456,7 @@ bool CWallet::SelectCoinsGroupedByAddresses(std::vector<CompactTallyItem>& vecTa
// otherwise they will just lead to higher fee / lower priority
if(wtx.tx->vout[i].nValue <= nSmallestDenom/10) continue;
// ignore mixed
if (GetCappedOutpointPrivateSendRounds(COutPoint(outpoint.hash, i)) >= CPrivateSendClientOptions::GetRounds()) continue;
if (IsFullyMixed(COutPoint(outpoint.hash, i))) continue;
}

if (itTallyItem == mapTally.end()) {
Expand Down Expand Up @@ -4194,6 +4230,8 @@ DBErrors CWallet::LoadWallet(bool& fFirstRunRet)
}
}

InitPrivateSendSalt();

if (nLoadWalletRet != DBErrors::LOAD_OK)
return nLoadWalletRet;

Expand Down
12 changes: 12 additions & 0 deletions src/wallet/wallet.h
Original file line number Diff line number Diff line change
Expand Up @@ -802,6 +802,17 @@ class CWallet final : public CCryptoKeyStore, public CValidationInterface
*/
const CBlockIndex* m_last_block_processed = nullptr;

/** Pulled from wallet DB ("ps_salt") and used when mixing a random number of rounds.
* This salt is needed to prevent an attacker from learning how many extra times
* the input was mixed based only on information in the blockchain.
*/
uint256 nPrivateSendSalt;

/**
* Fetches PrivateSend salt from database or generates and saves a new one if no salt was found in the db
*/
void InitPrivateSendSalt();

public:
/*
* Main wallet lock.
Expand Down Expand Up @@ -912,6 +923,7 @@ class CWallet final : public CCryptoKeyStore, public CValidationInterface
int GetCappedOutpointPrivateSendRounds(const COutPoint& outpoint) const;

bool IsDenominated(const COutPoint& outpoint) const;
bool IsFullyMixed(const COutPoint& outpoint) const;

bool IsSpent(const uint256& hash, unsigned int n) const;

Expand Down
10 changes: 10 additions & 0 deletions src/wallet/walletdb.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,16 @@ bool WalletBatch::WriteAccountingEntry(const uint64_t nAccEntryNum, const CAccou
return WriteIC(std::make_pair(std::string("acentry"), std::make_pair(acentry.strAccount, nAccEntryNum)), acentry);
}

bool WalletBatch::ReadPrivateSendSalt(uint256& salt)
{
return m_batch.Read(std::string("ps_salt"), salt);
}

bool WalletBatch::WritePrivateSendSalt(const uint256& salt)
{
return WriteIC(std::string("ps_salt"), salt);
}

CAmount WalletBatch::GetAccountCreditDebit(const std::string& strAccount)
{
std::list<CAccountingEntry> entries;
Expand Down
3 changes: 3 additions & 0 deletions src/wallet/walletdb.h
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,9 @@ class WalletBatch
bool ReadAccount(const std::string& strAccount, CAccount& account);
bool WriteAccount(const std::string& strAccount, const CAccount& account);

bool ReadPrivateSendSalt(uint256& salt);
bool WritePrivateSendSalt(const uint256& salt);

/// Write destination data key,value tuple to database
bool WriteDestData(const std::string &address, const std::string &key, const std::string &value);
/// Erase destination data tuple from wallet database
Expand Down