@@ -1337,13 +1337,83 @@ CAmount CalculateDeniabilizationFeeEstimate(const CTxDestination& shared_destina
13371337 return deniabilizationFee;
13381338}
13391339
1340- util::Result<CreatedTransactionResult> CreateDeniabilizationTransaction (
1341- CWallet& wallet,
1342- const std::set<COutPoint>& inputs,
1343- unsigned int confirm_target,
1344- unsigned int deniabilization_cycles,
1345- bool sign,
1346- bool & insufficient_amount)
1340+ std::pair<unsigned int , bool > CalculateDeniabilizationCycles (CWallet& wallet, const COutPoint& outpoint)
1341+ {
1342+ LOCK (wallet.cs_wallet );
1343+ auto walletTx = wallet.GetWalletTx (outpoint.hash );
1344+ if (!walletTx) {
1345+ return std::make_pair (0 , false );
1346+ }
1347+ auto tx = walletTx->tx ;
1348+
1349+ if (tx->IsCoinBase ()) {
1350+ // this is a block reward tx, so we tag it as such
1351+ return std::make_pair (0 , true );
1352+ }
1353+
1354+ // an deniabilized coin is one we sent to ourselves
1355+ // all txIn should belong to our wallet
1356+ if (tx->vin .empty ()) {
1357+ return std::make_pair (0 , false );
1358+ }
1359+ for (const auto & txIn : tx->vin ) {
1360+ if (InputIsMine (wallet, txIn) == ISMINE_NO) {
1361+ return std::make_pair (0 , false );
1362+ }
1363+ }
1364+
1365+ // all txOut should belong to our wallet
1366+ Assert (outpoint.n < tx->vout .size ());
1367+ uint n = 0 ;
1368+ for (const auto & txOut : tx->vout ) {
1369+ if (wallet.IsMine (txOut) == ISMINE_NO) {
1370+ Assert (n != outpoint.n );
1371+ return std::make_pair (0 , false );
1372+ }
1373+ n++;
1374+ }
1375+
1376+ uint uniqueTxOutCount = 0 ;
1377+ for (const auto & txOut : tx->vout ) {
1378+ // check if it's a valid destination
1379+ CTxDestination txOutDestination;
1380+ if (!ExtractDestination (txOut.scriptPubKey , txOutDestination)) {
1381+ continue ;
1382+ }
1383+
1384+ // don't count outputs that match any input addresses (eg it's change output)
1385+ bool matchesInput = false ;
1386+ for (const auto & txIn : tx->vin ) {
1387+ auto prevWalletTx = wallet.GetWalletTx (txIn.prevout .hash );
1388+ if (prevWalletTx && prevWalletTx->tx ->vout [txIn.prevout .n ].scriptPubKey == txOut.scriptPubKey ) {
1389+ matchesInput = true ;
1390+ break ;
1391+ }
1392+ }
1393+ if (matchesInput) {
1394+ continue ;
1395+ }
1396+
1397+ uniqueTxOutCount++;
1398+ }
1399+
1400+ // we consider two or more unique outputs an "deniabilization" of the coin
1401+ uint deniabilizationCycles = uniqueTxOutCount >= 2 ? 1 : 0 ;
1402+
1403+ // all txIn and txOut are from our wallet
1404+ // however if we have multiple txIn this was either an initial deniabilization of multiple UTXOs or the user manually merged deniabilized UTXOs
1405+ // in either case we don't need to recurse into parent transactions and we can return the calculated cycles
1406+ if (tx->vin .size () > 1 ) {
1407+ return std::make_pair (deniabilizationCycles, false );
1408+ }
1409+
1410+ const auto & txIn = tx->vin [0 ];
1411+ // now recursively calculate the deniabilization cycles of the input
1412+ auto inputStats = CalculateDeniabilizationCycles (wallet, txIn.prevout );
1413+ return std::make_pair (inputStats.first + deniabilizationCycles, inputStats.second );
1414+ };
1415+
1416+ util::Result<CreatedTransactionResult> CreateDeniabilizationTransaction (CWallet& wallet, const std::set<COutPoint>& inputs, unsigned int confirm_target, unsigned int deniabilization_cycles, bool sign, bool & insufficient_amount)
13471417{
13481418 if (inputs.empty ()) {
13491419 return util::Error{_ (" Inputs must not be empty" )};
0 commit comments