private Dictionary <int, WitScript> SignCoinJoin(ClientRound ongoingRound, Transaction unsignedCoinJoin) { TxOut[] myOutputs = unsignedCoinJoin.Outputs .Where(x => x.ScriptPubKey == ongoingRound.Registration.ChangeAddress.ScriptPubKey || ongoingRound.Registration.ActiveOutputs.Select(y => y.Address.ScriptPubKey).Contains(x.ScriptPubKey)) .ToArray(); Money amountBack = myOutputs.Sum(y => y.Value); // Make sure change is counted. Money minAmountBack = ongoingRound.CoinsRegistered.Sum(x => x.Amount); // Start with input sum. // Do outputs.lenght + 1 in case the server estimated the network fees wrongly due to insufficient data in an edge case. Money networkFeesAfterOutputs = ongoingRound.State.FeePerOutputs * (ongoingRound.Registration.AliceClient.RegisteredAddresses.Length + 1); // Use registered addresses here, because network fees are decided at inputregistration. Money networkFeesAfterInputs = ongoingRound.State.FeePerInputs * ongoingRound.Registration.CoinsRegistered.Count(); Money networkFees = networkFeesAfterOutputs + networkFeesAfterInputs; minAmountBack -= networkFees; // Minus miner fee. IOrderedEnumerable <(Money value, int count)> indistinguishableOutputs = unsignedCoinJoin.GetIndistinguishableOutputs(includeSingle: false).OrderByDescending(x => x.count); foreach ((Money value, int count)denomPair in indistinguishableOutputs) { var mineCount = myOutputs.Count(x => x.Value == denomPair.value); Money denomination = denomPair.value; int anonset = Math.Min(110, denomPair.count); // https://github.com/zkSNACKs/WalletWasabi/issues/1379 Money expectedCoordinatorFee = denomination.Percentage(ongoingRound.State.CoordinatorFeePercent * anonset); for (int i = 0; i < mineCount; i++) { minAmountBack -= expectedCoordinatorFee; // Minus expected coordinator fee. } } // If there's no change output then coordinator protection may happened: bool gotChange = myOutputs.Select(x => x.ScriptPubKey).Contains(ongoingRound.Registration.ChangeAddress.ScriptPubKey); if (!gotChange) { Money minimumOutputAmount = Money.Coins(0.0001m); // If the change would be less than about $1 then add it to the coordinator. Money baseDenomination = indistinguishableOutputs.First().value; Money onePercentOfDenomination = baseDenomination.Percentage(1m); // If the change is less than about 1% of the newDenomination then add it to the coordinator fee. Money minimumChangeAmount = Math.Max(minimumOutputAmount, onePercentOfDenomination); minAmountBack -= minimumChangeAmount; // Minus coordinator protections (so it won't create bad coinjoins.) } if (amountBack < minAmountBack && !amountBack.Almost(minAmountBack, Money.Satoshis(1000))) // Just in case. Rounding error maybe? { Money diff = minAmountBack - amountBack; throw new NotSupportedException($"Coordinator did not add enough value to our outputs in the coinjoin. Missing: {diff.Satoshi} satoshis."); } var signedCoinJoin = unsignedCoinJoin.Clone(); signedCoinJoin.Sign( ongoingRound .CoinsRegistered .Select(x => (x.Secret ??= KeyManager.GetSecrets(SaltSoup(), x.ScriptPubKey).Single()) .PrivateKey .GetBitcoinSecret(Network)), ongoingRound .Registration .CoinsRegistered .Select(x => x.GetCoin())); var myDic = new Dictionary <int, WitScript>(); for (int i = 0; i < signedCoinJoin.Inputs.Count; i++) { var input = signedCoinJoin.Inputs[i]; if (ongoingRound.CoinsRegistered.Select(x => x.GetOutPoint()).Contains(input.PrevOut)) { myDic.Add(i, signedCoinJoin.Inputs[i].WitScript); } } return(myDic); }