Esempio n. 1
0
        public async Task <IEnumerable <Alice> > RemoveAlicesIfInputsSpentAsync()
        {
            var alicesRemoved = new List <Alice>();

            using (RoundSyncronizerLock.Lock())
            {
                if ((Phase != CcjRoundPhase.InputRegistration && Phase != CcjRoundPhase.ConnectionConfirmation) || Status != CcjRoundStatus.Running)
                {
                    throw new InvalidOperationException("Removing Alice is only allowed in InputRegistration and ConnectionConfirmation phases.");
                }

                foreach (Alice alice in Alices)
                {
                    foreach (OutPoint input in alice.Inputs.Select(y => y.OutPoint))
                    {
                        GetTxOutResponse getTxOutResponse = await RpcClient.GetTxOutAsync(input.Hash, (int)input.N, includeMempool : true);

                        // Check if inputs are unspent.
                        if (getTxOutResponse == null)
                        {
                            alicesRemoved.Add(alice);
                            Alices.Remove(alice);
                        }
                    }
                }
            }

            foreach (var alice in alicesRemoved)
            {
                Logger.LogInfo <CcjRound>($"Round ({RoundId}): Alice ({alice.UniqueId}) removed.");
            }

            return(alicesRemoved);
        }
Esempio n. 2
0
        public async Task CanGetTxOutFromRPCAsync()
        {
            using (var builder = NodeBuilder.Create())
            {
                var rpc = builder.CreateNode().CreateRPCClient();
                builder.StartAll();

                // 1. Generate some blocks and check if gettxout gives the right outputs for the first coin
                var       blocksToGenerate = 101;
                uint256[] blockHashes      = await rpc.GenerateAsync(blocksToGenerate);

                var txId = rpc.GetTransactions(blockHashes.First()).First().GetHash();
                GetTxOutResponse getTxOutResponse = await rpc.GetTxOutAsync(txId, 0);

                Assert.NotNull(getTxOutResponse);                 // null if spent
                Assert.Equal(blockHashes.Last(), getTxOutResponse.BestBlock);
                Assert.Equal(getTxOutResponse.Confirmations, blocksToGenerate);
                Assert.Equal(Money.Coins(50), getTxOutResponse.TxOut.Value);
                Assert.NotNull(getTxOutResponse.TxOut.ScriptPubKey);
                Assert.Equal("pubkey", getTxOutResponse.ScriptPubKeyType);
                Assert.True(getTxOutResponse.IsCoinBase);

                // 2. Spend the first coin
                var   address    = new Key().PubKey.GetAddress(rpc.Network);
                Money sendAmount = Money.Parse("49");
                txId = await rpc.SendToAddressAsync(address, sendAmount);

                // 3. Make sure if we don't include the mempool into the database the txo will not be considered utxo
                getTxOutResponse = await rpc.GetTxOutAsync(txId, 0, false);

                Assert.Null(getTxOutResponse);

                // 4. Find the output index we want to check
                var tx    = rpc.GetRawTransaction(txId);
                int index = -1;
                for (int i = 0; i < tx.Outputs.Count; i++)
                {
                    if (tx.Outputs[i].Value == sendAmount)
                    {
                        index = i;
                    }
                }
                Assert.NotEqual(index, -1);

                // 5. Make sure the expected amounts are received for unconfirmed transactions
                getTxOutResponse = await rpc.GetTxOutAsync(txId, index, true);

                Assert.NotNull(getTxOutResponse);                 // null if spent
                Assert.Equal(blockHashes.Last(), getTxOutResponse.BestBlock);
                Assert.Equal(getTxOutResponse.Confirmations, 0);
                Assert.Equal(Money.Coins(49), getTxOutResponse.TxOut.Value);
                Assert.NotNull(getTxOutResponse.TxOut.ScriptPubKey);
                Assert.Equal("pubkeyhash", getTxOutResponse.ScriptPubKeyType);
                Assert.False(getTxOutResponse.IsCoinBase);
            }
        }
Esempio n. 3
0
        public UtxoReferee(Network network, string folderPath, RPCClient rpc, CoordinatorRoundConfig roundConfig)
        {
            Network     = Guard.NotNull(nameof(network), network);
            FolderPath  = Guard.NotNullOrEmptyOrWhitespace(nameof(folderPath), folderPath, trim: true);
            RpcClient   = Guard.NotNull(nameof(rpc), rpc);
            RoundConfig = Guard.NotNull(nameof(roundConfig), roundConfig);

            BannedUtxos = new ConcurrentDictionary <OutPoint, BannedUtxo>();

            Directory.CreateDirectory(FolderPath);

            if (File.Exists(BannedUtxosFilePath))
            {
                try
                {
                    var      toRemove = new List <string>();               // what's been confirmed
                    string[] allLines = File.ReadAllLines(BannedUtxosFilePath);
                    foreach (string line in allLines)
                    {
                        var bannedRecord = BannedUtxo.FromString(line);

                        GetTxOutResponse getTxOutResponse = RpcClient.GetTxOut(bannedRecord.Utxo.Hash, (int)bannedRecord.Utxo.N, includeMempool: true);

                        // Check if inputs are unspent.
                        if (getTxOutResponse is null)
                        {
                            toRemove.Add(line);
                        }
                        else
                        {
                            BannedUtxos.TryAdd(bannedRecord.Utxo, bannedRecord);
                        }
                    }

                    if (toRemove.Count != 0)                     // a little performance boost, often it'll be empty
                    {
                        var newAllLines = allLines.Where(x => !toRemove.Contains(x));
                        File.WriteAllLines(BannedUtxosFilePath, newAllLines);
                    }

                    Logger.LogInfo($"{allLines.Length} banned UTXOs are loaded from {BannedUtxosFilePath}.");
                }
                catch (Exception ex)
                {
                    Logger.LogWarning($"Banned UTXO file got corrupted. Deleting {BannedUtxosFilePath}. {ex.GetType()}: {ex.Message}");
                    File.Delete(BannedUtxosFilePath);
                }
            }
            else
            {
                Logger.LogInfo($"No banned UTXOs are loaded from {BannedUtxosFilePath}.");
            }
        }
        public async Task <IActionResult> PostInputsAsync([FromBody] InputsRequest request)
        {
            // Validate request.
            if (!ModelState.IsValid ||
                request == null ||
                string.IsNullOrWhiteSpace(request.BlindedOutputScriptHex) ||
                string.IsNullOrWhiteSpace(request.ChangeOutputScript) ||
                request.Inputs == null ||
                request.Inputs.Count() == 0 ||
                request.Inputs.Any(x => x.Input == null ||
                                   x.Input.Hash == null ||
                                   string.IsNullOrWhiteSpace(x.Proof)))
            {
                return(BadRequest("Invalid request."));
            }

            if (request.Inputs.Count() > 7)
            {
                return(BadRequest("Maximum 7 inputs can be registered."));
            }

            using (await InputsLock.LockAsync())
            {
                CcjRound round = Coordinator.GetCurrentInputRegisterableRound();

                // Do more checks.
                try
                {
                    if (round.ContainsBlindedOutputScriptHex(request.BlindedOutputScriptHex, out _))
                    {
                        return(BadRequest("Blinded output has already been registered."));
                    }

                    var changeOutput = new Script(request.ChangeOutputScript);

                    var inputs = new HashSet <(OutPoint OutPoint, TxOut Output)>();

                    var alicesToRemove = new HashSet <Guid>();

                    foreach (InputProofModel inputProof in request.Inputs)
                    {
                        if (inputs.Any(x => x.OutPoint == inputProof.Input))
                        {
                            return(BadRequest("Cannot register an input twice."));
                        }
                        if (round.ContainsInput(inputProof.Input, out List <Alice> tr))
                        {
                            alicesToRemove.UnionWith(tr.Select(x => x.UniqueId));                             // Input is already registered by this alice, remove it later if all the checks are completed fine.
                        }
                        if (Coordinator.AnyRunningRoundContainsInput(inputProof.Input, out List <Alice> tnr))
                        {
                            if (tr.Union(tnr).Count() > tr.Count())
                            {
                                return(BadRequest("Input is already registered in another round."));
                            }
                        }

                        var bannedElem = Coordinator.UtxoReferee.BannedUtxos.SingleOrDefault(x => x.Key == inputProof.Input);
                        if (bannedElem.Key != default)
                        {
                            int maxBan  = (int)TimeSpan.FromDays(30).TotalMinutes;
                            int banLeft = maxBan - (int)((DateTimeOffset.UtcNow - bannedElem.Value.timeOfBan).TotalMinutes);
                            if (banLeft > 0)
                            {
                                return(BadRequest($"Input is banned from participation for {banLeft} minutes: {inputProof.Input.N}:{inputProof.Input.Hash}."));
                            }
                            else
                            {
                                await Coordinator.UtxoReferee.UnbanAsync(bannedElem.Key);
                            }
                        }

                        GetTxOutResponse getTxOutResponse = await RpcClient.GetTxOutAsync(inputProof.Input.Hash, (int)inputProof.Input.N, includeMempool : true);

                        // Check if inputs are unspent.
                        if (getTxOutResponse == null)
                        {
                            return(BadRequest("Provided input is not unspent."));
                        }

                        // Check if unconfirmed.
                        if (getTxOutResponse.Confirmations <= 0)
                        {
                            // If it spends a CJ then it may be acceptable to register.
                            if (!Coordinator.ContainsCoinJoin(inputProof.Input.Hash))
                            {
                                return(BadRequest("Provided input is neither confirmed, nor is from an unconfirmed coinjoin."));
                            }
                            // After 24 unconfirmed cj in the mempool dont't let unconfirmed coinjoin to be registered.
                            if (await Coordinator.IsUnconfirmedCoinJoinLimitReachedAsync())
                            {
                                return(BadRequest("Provided input is from an unconfirmed coinjoin, but the maximum number of unconfirmed coinjoins is reached."));
                            }
                        }

                        // Check if immature.
                        if (getTxOutResponse.Confirmations <= 100)
                        {
                            if (getTxOutResponse.IsCoinBase)
                            {
                                return(BadRequest("Provided input is immature."));
                            }
                        }

                        // Check if inputs are native segwit.
                        if (getTxOutResponse.ScriptPubKeyType != "witness_v0_keyhash")
                        {
                            return(BadRequest("Provided input must be witness_v0_keyhash."));
                        }

                        TxOut txout = getTxOutResponse.TxOut;

                        var address = (BitcoinWitPubKeyAddress)txout.ScriptPubKey.GetDestinationAddress(Network);
                        // Check if proofs are valid.
                        bool validProof;
                        try
                        {
                            validProof = address.VerifyMessage(request.BlindedOutputScriptHex, inputProof.Proof);
                        }
                        catch (FormatException ex)
                        {
                            return(BadRequest($"Provided proof is invalid: {ex.Message}"));
                        }
                        if (!validProof)
                        {
                            return(BadRequest("Provided proof is invalid."));
                        }

                        inputs.Add((inputProof.Input, txout));
                    }

                    // Check if inputs have enough coins.
                    Money inputSum        = inputs.Sum(x => x.Output.Value);
                    Money networkFeeToPay = (inputs.Count() * round.FeePerInputs + 2 * round.FeePerOutputs);
                    Money changeAmount    = inputSum - (round.Denomination + networkFeeToPay);
                    if (changeAmount < Money.Zero)
                    {
                        return(BadRequest($"Not enough inputs are provided. Fee to pay: {networkFeeToPay.ToString(false, true)} BTC. Round denomination: {round.Denomination.ToString(false, true)} BTC. Only provided: {inputSum.ToString(false, true)} BTC."));
                    }

                    // Make sure Alice checks work.
                    var alice = new Alice(inputs, networkFeeToPay, new Script(request.ChangeOutputScript), request.BlindedOutputScriptHex);

                    foreach (Guid aliceToRemove in alicesToRemove)
                    {
                        round.RemoveAlicesBy(aliceToRemove);
                    }
                    round.AddAlice(alice);

                    // All checks are good. Sign.
                    byte[] blindedData;
                    try
                    {
                        blindedData = ByteHelpers.FromHex(request.BlindedOutputScriptHex);
                    }
                    catch
                    {
                        return(BadRequest("Invalid blinded output hex."));
                    }
                    Logger.LogDebug <ChaumianCoinJoinController>($"Blinded data hex: {request.BlindedOutputScriptHex}");
                    Logger.LogDebug <ChaumianCoinJoinController>($"Blinded data array size: {blindedData.Length}");
                    byte[] signature = RsaKey.SignBlindedData(blindedData);

                    // Check if phase changed since.
                    if (round.Status != ChaumianCoinJoin.CcjRoundStatus.Running || round.Phase != CcjRoundPhase.InputRegistration)
                    {
                        return(base.StatusCode(StatusCodes.Status503ServiceUnavailable, "The state of the round changed while handling the request. Try again."));
                    }

                    // Progress round if needed.
                    if (round.CountAlices() >= round.AnonymitySet)
                    {
                        await round.RemoveAlicesIfInputsSpentAsync();

                        if (round.CountAlices() >= round.AnonymitySet)
                        {
                            await round.ExecuteNextPhaseAsync(CcjRoundPhase.ConnectionConfirmation);
                        }
                    }

                    var resp = new InputsResponse
                    {
                        UniqueId = alice.UniqueId,
                        BlindedOutputSignature = signature,
                        RoundId = round.RoundId
                    };
                    return(Ok(resp));
                }
                catch (Exception ex)
                {
                    Logger.LogDebug <ChaumianCoinJoinController>(ex);
                    return(BadRequest(ex.Message));
                }
            }
        }
Esempio n. 5
0
        public async Task BanningTestsAsync()
        {
            (_, IRPCClient rpc, Network network, Coordinator coordinator, _, _, _) = await Common.InitializeTestEnvironmentAsync(RegTestFixture, 1);

            Money   denomination                  = Money.Coins(0.1m);
            decimal coordinatorFeePercent         = 0.1m;
            int     anonymitySet                  = 3;
            int     connectionConfirmationTimeout = 120;
            var     roundConfig = RegTestFixture.CreateRoundConfig(denomination, 140, 0.7, coordinatorFeePercent, anonymitySet, 240, connectionConfirmationTimeout, 1, 1, 1, 24, true, 11);

            coordinator.RoundConfig.UpdateOrDefault(roundConfig, toFile: true);
            coordinator.AbortAllRoundsInInputRegistration("");

            await rpc.GenerateAsync(3);             // So to make sure we have enough money.

            Uri baseUri                = new Uri(RegTestFixture.BackendEndPoint);
            var fundingTxCount         = 0;
            var inputRegistrationUsers = new List <(Requester requester, BlindedOutputWithNonceIndex blinded, BitcoinAddress activeOutputAddress, BitcoinAddress changeOutputAddress, IEnumerable <InputProofModel> inputProofModels, List <(Key key, BitcoinWitPubKeyAddress address, uint256 txHash, Transaction tx, OutPoint input)> userInputData)>();
            CoordinatorRound round     = null;

            for (int i = 0; i < roundConfig.AnonymitySet; i++)
            {
                var userInputData       = new List <(Key key, BitcoinWitPubKeyAddress inputAddress, uint256 txHash, Transaction tx, OutPoint input)>();
                var activeOutputAddress = new Key().PubKey.GetAddress(ScriptPubKeyType.Segwit, network);
                var changeOutputAddress = new Key().PubKey.GetAddress(ScriptPubKeyType.Segwit, network);
                round = coordinator.GetCurrentInputRegisterableRoundOrDefault();
                Requester requester = new Requester();
                var       nonce     = round.NonceProvider.GetNextNonce();

                var     blinded = new BlindedOutputWithNonceIndex(nonce.N, requester.BlindScript(round.MixingLevels.GetBaseLevel().SignerKey.PubKey, nonce.R, activeOutputAddress.ScriptPubKey));
                uint256 blindedOutputScriptsHash = new uint256(Hashes.SHA256(blinded.BlindedOutput.ToBytes()));

                var inputProofModels  = new List <InputProofModel>();
                int numberOfInputs    = CryptoHelpers.RandomInt(1, 6);
                var receiveSatoshiSum = 0;
                for (int j = 0; j < numberOfInputs; j++)
                {
                    var key            = new Key();
                    var receiveSatoshi = CryptoHelpers.RandomInt(1000, 100000000);
                    receiveSatoshiSum += receiveSatoshi;
                    if (j == numberOfInputs - 1)
                    {
                        receiveSatoshi = 100000000;
                    }
                    BitcoinWitPubKeyAddress inputAddress = key.PubKey.GetSegwitAddress(network);
                    uint256 txHash = await rpc.SendToAddressAsync(inputAddress, Money.Satoshis(receiveSatoshi));

                    fundingTxCount++;
                    Assert.NotNull(txHash);
                    Transaction transaction = await rpc.GetRawTransactionAsync(txHash);

                    var coin = transaction.Outputs.GetCoins(inputAddress.ScriptPubKey).Single();

                    OutPoint input      = coin.Outpoint;
                    var      inputProof = new InputProofModel {
                        Input = input, Proof = key.SignCompact(blindedOutputScriptsHash)
                    };
                    inputProofModels.Add(inputProof);

                    GetTxOutResponse getTxOutResponse = await rpc.GetTxOutAsync(input.Hash, (int)input.N, includeMempool : true);

                    // Check if inputs are unspent.
                    Assert.NotNull(getTxOutResponse);

                    userInputData.Add((key, inputAddress, txHash, transaction, input));
                }

                inputRegistrationUsers.Add((requester, blinded, activeOutputAddress, changeOutputAddress, inputProofModels, userInputData));
            }

            var mempool = await rpc.GetRawMempoolAsync();

            Assert.Equal(inputRegistrationUsers.SelectMany(x => x.userInputData).Count(), mempool.Length);

            while ((await rpc.GetRawMempoolAsync()).Length != 0)
            {
                await rpc.GenerateAsync(1);
            }

            var aliceClients = new List <Task <AliceClient4> >();

            foreach (var user in inputRegistrationUsers)
            {
                aliceClients.Add(AliceClientBase.CreateNewAsync(round.RoundId, new[] { user.activeOutputAddress }, new[] { round.MixingLevels.GetBaseLevel().SignerKey.PubKey }, new[] { user.requester }, network, user.changeOutputAddress, new[] { user.blinded }, user.inputProofModels, BackendHttpClient));
            }

            long roundId = 0;
            var  users   = new List <(Requester requester, BlindedOutputWithNonceIndex blinded, BitcoinAddress activeOutputAddress, BitcoinAddress changeOutputAddress, IEnumerable <InputProofModel> inputProofModels, List <(Key key, BitcoinWitPubKeyAddress address, uint256 txHash, Transaction tx, OutPoint input)> userInputData, AliceClient4 aliceClient, UnblindedSignature unblindedSignature)>();

            for (int i = 0; i < inputRegistrationUsers.Count; i++)
            {
                var user    = inputRegistrationUsers[i];
                var request = aliceClients[i];

                var aliceClient = await request;

                if (roundId == 0)
                {
                    roundId = aliceClient.RoundId;
                }
                else
                {
                    Assert.Equal(roundId, aliceClient.RoundId);
                }

                // Because it's valuetuple.
                users.Add((user.requester, user.blinded, user.activeOutputAddress, user.changeOutputAddress, user.inputProofModels, user.userInputData, aliceClient, null));
            }

            Assert.Equal(users.Count, roundConfig.AnonymitySet);

            var confirmationRequests = new List <Task <(RoundPhase currentPhase, IEnumerable <ActiveOutput>)> >();

            foreach (var user in users)
            {
                confirmationRequests.Add(user.aliceClient.PostConfirmationAsync());
            }

            RoundPhase roundPhase = RoundPhase.InputRegistration;
            int        k          = 0;

            foreach (var request in confirmationRequests)
            {
                var resp = await request;
                if (roundPhase == RoundPhase.InputRegistration)
                {
                    roundPhase = resp.currentPhase;
                }
                else
                {
                    Assert.Equal(roundPhase, resp.currentPhase);
                }

                var user = users.ElementAt(k);
                user.unblindedSignature = resp.Item2.First().Signature;
            }

            {
                var times = 0;
                while (!(await SatoshiClient.GetAllRoundStatesAsync()).All(x => x.Phase == RoundPhase.InputRegistration))
                {
                    await Task.Delay(100);

                    if (times > 50)                     // 5 sec, 3 should be enough
                    {
                        throw new TimeoutException("Not all rounds were in InputRegistration.");
                    }
                    times++;
                }
            }

            int bannedCount = coordinator.UtxoReferee.CountBanned(false);

            Assert.Equal(0, bannedCount);

            aliceClients.Clear();
            round = coordinator.GetCurrentInputRegisterableRoundOrDefault();
            foreach (var user in inputRegistrationUsers)
            {
                aliceClients.Add(AliceClientBase.CreateNewAsync(round.RoundId, new[] { user.activeOutputAddress }, new[] { round.MixingLevels.GetBaseLevel().SignerKey.PubKey }, new[] { user.requester }, network, user.changeOutputAddress, new[] { user.blinded }, user.inputProofModels, BackendHttpClient));
            }

            roundId = 0;
            users   = new List <(Requester requester, BlindedOutputWithNonceIndex blinded, BitcoinAddress activeOutputAddress, BitcoinAddress changeOutputAddress, IEnumerable <InputProofModel> inputProofModels, List <(Key key, BitcoinWitPubKeyAddress address, uint256 txHash, Transaction tx, OutPoint input)> userInputData, AliceClient4 aliceClient, UnblindedSignature unblindedSignature)>();
            for (int i = 0; i < inputRegistrationUsers.Count; i++)
            {
                var user    = inputRegistrationUsers[i];
                var request = aliceClients[i];

                var aliceClient = await request;
                if (roundId == 0)
                {
                    roundId = aliceClient.RoundId;
                }
                else
                {
                    Assert.Equal(roundId, aliceClient.RoundId);
                }

                // Because it's valuetuple.
                users.Add((user.requester, user.blinded, user.activeOutputAddress, user.changeOutputAddress, user.inputProofModels, user.userInputData, aliceClient, null));
            }

            Assert.Equal(users.Count, roundConfig.AnonymitySet);

            confirmationRequests = new List <Task <(RoundPhase currentPhase, IEnumerable <ActiveOutput>)> >();

            foreach (var user in users)
            {
                confirmationRequests.Add(user.aliceClient.PostConfirmationAsync());
            }

            {
                var times = 0;
                while (!(await SatoshiClient.GetAllRoundStatesAsync()).All(x => x.Phase == RoundPhase.InputRegistration))
                {
                    await Task.Delay(100);

                    if (times > 50)                     // 5 sec, 3 should be enough
                    {
                        throw new TimeoutException("Not all rounds were in InputRegistration.");
                    }
                    times++;
                }
            }

            bannedCount = coordinator.UtxoReferee.CountBanned(false);
            Assert.True(bannedCount >= roundConfig.AnonymitySet);

            foreach (var aliceClient in aliceClients)
            {
                aliceClient?.Dispose();
            }
        }
Esempio n. 6
0
        public async Task <IActionResult> PostInputsAsync([FromBody] InputsRequest request)
        {
            // Validate request.
            if (!ModelState.IsValid ||
                request is null ||
                string.IsNullOrWhiteSpace(request.BlindedOutputScriptHex) ||
                string.IsNullOrWhiteSpace(request.ChangeOutputAddress) ||
                request.Inputs is null ||
                !request.Inputs.Any() ||
                request.Inputs.Any(x => x.Input == default(TxoRef) ||
                                   x.Input.TransactionId is null ||
                                   string.IsNullOrWhiteSpace(x.Proof)))
            {
                return(BadRequest("Invalid request."));
            }

            if (request.Inputs.Count() > 7)
            {
                return(BadRequest("Maximum 7 inputs can be registered."));
            }

            using (await InputsLock.LockAsync())
            {
                CcjRound round = Coordinator.GetCurrentInputRegisterableRound();

                // Do more checks.
                try
                {
                    if (round.ContainsBlindedOutputScriptHex(request.BlindedOutputScriptHex, out _))
                    {
                        return(BadRequest("Blinded output has already been registered."));
                    }

                    BitcoinAddress changeOutputAddress;
                    try
                    {
                        changeOutputAddress = BitcoinAddress.Create(request.ChangeOutputAddress, Network);
                    }
                    catch (FormatException ex)
                    {
                        return(BadRequest($"Invalid ChangeOutputAddress. Details: {ex.Message}"));
                    }

                    var inputs = new HashSet <Coin>();

                    var alicesToRemove = new HashSet <Guid>();

                    foreach (InputProofModel inputProof in request.Inputs)
                    {
                        if (inputs.Any(x => x.Outpoint == inputProof.Input))
                        {
                            return(BadRequest("Cannot register an input twice."));
                        }
                        if (round.ContainsInput(inputProof.Input.ToOutPoint(), out List <Alice> tr))
                        {
                            alicesToRemove.UnionWith(tr.Select(x => x.UniqueId));                             // Input is already registered by this alice, remove it later if all the checks are completed fine.
                        }
                        if (Coordinator.AnyRunningRoundContainsInput(inputProof.Input.ToOutPoint(), out List <Alice> tnr))
                        {
                            if (tr.Union(tnr).Count() > tr.Count())
                            {
                                return(BadRequest("Input is already registered in another round."));
                            }
                        }

                        OutPoint outpoint   = inputProof.Input.ToOutPoint();
                        var      bannedElem = await Coordinator.UtxoReferee.TryGetBannedAsync(outpoint, notedToo : false);

                        if (bannedElem != null)
                        {
                            return(BadRequest($"Input is banned from participation for {(int)bannedElem.Value.bannedRemaining.TotalMinutes} minutes: {inputProof.Input.Index}:{inputProof.Input.TransactionId}."));
                        }

                        GetTxOutResponse getTxOutResponse = await RpcClient.GetTxOutAsync(inputProof.Input.TransactionId, (int)inputProof.Input.Index, includeMempool : true);

                        // Check if inputs are unspent.
                        if (getTxOutResponse is null)
                        {
                            return(BadRequest($"Provided input is not unspent: {inputProof.Input.Index}:{inputProof.Input.TransactionId}."));
                        }

                        // Check if unconfirmed.
                        if (getTxOutResponse.Confirmations <= 0)
                        {
                            // If it spends a CJ then it may be acceptable to register.
                            if (!Coordinator.ContainsCoinJoin(inputProof.Input.TransactionId))
                            {
                                return(BadRequest("Provided input is neither confirmed, nor is from an unconfirmed coinjoin."));
                            }

                            // Check if mempool would accept a fake transaction created with the registered inputs.
                            // This will catch ascendant/descendant count and size limits for example.
                            var result = await RpcClient.TestMempoolAcceptAsync(new Coin(inputProof.Input.ToOutPoint(), getTxOutResponse.TxOut));

                            if (!result.accept)
                            {
                                return(BadRequest($"Provided input is from an unconfirmed coinjoin, but a limit is reached: {result.rejectReason}"));
                            }
                        }

                        // Check if immature.
                        if (getTxOutResponse.Confirmations <= 100)
                        {
                            if (getTxOutResponse.IsCoinBase)
                            {
                                return(BadRequest("Provided input is immature."));
                            }
                        }

                        // Check if inputs are native segwit.
                        if (getTxOutResponse.ScriptPubKeyType != "witness_v0_keyhash")
                        {
                            return(BadRequest("Provided input must be witness_v0_keyhash."));
                        }

                        TxOut txout = getTxOutResponse.TxOut;

                        var address = (BitcoinWitPubKeyAddress)txout.ScriptPubKey.GetDestinationAddress(Network);
                        // Check if proofs are valid.
                        bool validProof;
                        try
                        {
                            validProof = address.VerifyMessage(request.BlindedOutputScriptHex, inputProof.Proof);
                        }
                        catch (FormatException ex)
                        {
                            return(BadRequest($"Provided proof is invalid: {ex.Message}"));
                        }
                        if (!validProof)
                        {
                            await Coordinator.UtxoReferee.BanUtxosAsync(1, DateTimeOffset.UtcNow, forceNoted : false, round.RoundId, outpoint);

                            return(BadRequest("Provided proof is invalid."));
                        }

                        inputs.Add(new Coin(inputProof.Input.ToOutPoint(), txout));
                    }

                    // Check if inputs have enough coins.
                    Money inputSum        = inputs.Sum(x => x.Amount);
                    Money networkFeeToPay = (inputs.Count() * round.FeePerInputs) + (2 * round.FeePerOutputs);
                    Money changeAmount    = inputSum - (round.Denomination + networkFeeToPay);
                    if (changeAmount < Money.Zero)
                    {
                        return(BadRequest($"Not enough inputs are provided. Fee to pay: {networkFeeToPay.ToString(false, true)} BTC. Round denomination: {round.Denomination.ToString(false, true)} BTC. Only provided: {inputSum.ToString(false, true)} BTC."));
                    }

                    // Make sure Alice checks work.
                    var alice = new Alice(inputs, networkFeeToPay, changeOutputAddress, request.BlindedOutputScriptHex);

                    foreach (Guid aliceToRemove in alicesToRemove)
                    {
                        round.RemoveAlicesBy(aliceToRemove);
                    }
                    round.AddAlice(alice);

                    // All checks are good. Sign.
                    byte[] blindedData;
                    try
                    {
                        blindedData = ByteHelpers.FromHex(request.BlindedOutputScriptHex);
                    }
                    catch
                    {
                        return(BadRequest("Invalid blinded output hex."));
                    }

                    byte[] signature = RsaKey.SignBlindedData(blindedData);

                    // Check if phase changed since.
                    if (round.Status != CcjRoundStatus.Running || round.Phase != CcjRoundPhase.InputRegistration)
                    {
                        return(base.StatusCode(StatusCodes.Status503ServiceUnavailable, "The state of the round changed while handling the request. Try again."));
                    }

                    // Progress round if needed.
                    if (round.CountAlices() >= round.AnonymitySet)
                    {
                        await round.RemoveAlicesIfAnInputRefusedByMempoolAsync();

                        if (round.CountAlices() >= round.AnonymitySet)
                        {
                            await round.ExecuteNextPhaseAsync(CcjRoundPhase.ConnectionConfirmation);
                        }
                    }

                    var resp = new InputsResponse
                    {
                        UniqueId = alice.UniqueId,
                        BlindedOutputSignature = signature,
                        RoundId = round.RoundId
                    };
                    return(Ok(resp));
                }
                catch (Exception ex)
                {
                    Logger.LogDebug <ChaumianCoinJoinController>(ex);
                    return(BadRequest(ex.Message));
                }
            }
        }