Skip to content

Commit

Permalink
Merge pull request #3702 from PastaPastaPasta/backport-v16-3661
Browse files Browse the repository at this point in the history
[v0.16.x] Backport #3661
  • Loading branch information
UdjinM6 authored Sep 13, 2020
2 parents 5c28615 + 08f2437 commit 9ded950
Show file tree
Hide file tree
Showing 10 changed files with 92 additions and 19 deletions.
2 changes: 1 addition & 1 deletion src/privatesend/privatesend-client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1199,7 +1199,7 @@ bool CPrivateSendClientSession::SubmitDenominate(CConnman& connman)

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

for (int i = 0; i < privateSendClient.nPrivateSendRounds; i++) {
for (int i = 0; i < privateSendClient.nPrivateSendRounds + privateSendClient.nPrivateSendRandomRounds; 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
8 changes: 6 additions & 2 deletions src/privatesend/privatesend-client.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,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 CPrivateSendClientManager privateSendClient;
Expand Down Expand Up @@ -204,15 +206,16 @@ class CPrivateSendClientManager : public CPrivateSendBaseManager
public:
int nPrivateSendSessions;
int nPrivateSendRounds;
int nPrivateSendRandomRounds;
int nPrivateSendAmount;
int nPrivateSendDenomsGoal;
int nPrivateSendDenomsHardCap;
bool fEnablePrivateSend;
bool fPrivateSendRunning;
bool fPrivateSendMultiSession;

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 All @@ -222,6 +225,7 @@ class CPrivateSendClientManager : public CPrivateSendBaseManager
strAutoDenomResult(),
nCachedBlockHeight(0),
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 @@ -724,9 +724,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 >= privateSendClient.nPrivateSendRounds) || !(coinControl()->IsUsingPrivateSend())) {
if ((coinControl()->IsUsingPrivateSend() && model->isFullyMixed(outpoint)) || !(coinControl()->IsUsingPrivateSend())) {
nSum += out.tx->tx->vout[out.i].nValue;
nChildren++;

Expand Down Expand Up @@ -777,6 +776,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 @@ -234,6 +234,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 @@ -230,6 +230,7 @@ class WalletModel : public QObject
int getNumISLocks() const;

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

private:
CWallet *wallet;
Expand Down
2 changes: 1 addition & 1 deletion src/wallet/rpcwallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3188,7 +3188,7 @@ UniValue listunspent(const JSONRPCRequest& request)
entry.push_back(Pair("spendable", out.fSpendable));
entry.push_back(Pair("solvable", out.fSolvable));
entry.push_back(Pair("safe", out.fSafe));
entry.push_back(Pair("ps_rounds", pwallet->GetCappedOutpointPrivateSendRounds(COutPoint(out.tx->GetHash(), out.i))));
entry.push_back(Pair("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 @@ -1577,9 +1577,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 + privateSendClient.nPrivateSendRandomRounds;

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

auto pair = mapOutpointRoundsCache.emplace(outpoint, -10);
Expand Down Expand Up @@ -1645,7 +1647,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 @@ -1675,6 +1677,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 < privateSendClient.nPrivateSendRounds) 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 < privateSendClient.nPrivateSendRounds + privateSendClient.nPrivateSendRandomRounds) {
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 @@ -2317,8 +2342,7 @@ CAmount CWalletTx::GetAnonymizedCredit(const CCoinControl* coinControl) const

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

const int nRounds = pwallet->GetCappedOutpointPrivateSendRounds(outpoint);
if (nRounds >= privateSendClient.nPrivateSendRounds){
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 @@ -2787,12 +2811,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 >= privateSendClient.nPrivateSendRounds;
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 < privateSendClient.nPrivateSendRounds;
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 @@ -2909,6 +2931,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 @@ -3174,8 +3211,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 < privateSendClient.nPrivateSendRounds) continue;
if (!IsFullyMixed(outpoint)) continue;
}
nValueFromPresetInputs += pcoin->tx->vout[outpoint.n].nValue;
setPresetCoins.insert(CInputCoin(pcoin, outpoint.n));
Expand Down Expand Up @@ -3376,7 +3412,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)) >= privateSendClient.nPrivateSendRounds) continue;
if (IsFullyMixed(COutPoint(outpoint.hash, i))) continue;
}

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

InitPrivateSendSalt();

if (nLoadWalletRet != DB_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 @@ -817,6 +817,17 @@ class CWallet final : public CCryptoKeyStore, public CValidationInterface
*/
const CBlockIndex* m_last_block_processed;

/** 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 @@ -949,6 +960,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

0 comments on commit 9ded950

Please sign in to comment.