Esempio n. 1
0
        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);
        }