private (CcjRound round, Alice alice) GetRunningRoundAndAliceOrFailureResponse(long roundId, string uniqueId, out IActionResult returnFailureResponse)
        {
            returnFailureResponse = null;

            Guid uniqueIdGuid = GetGuidOrFailureResponse(uniqueId, out IActionResult guidFail);

            if (guidFail != null)
            {
                returnFailureResponse = guidFail;
                return(null, null);
            }

            CcjRound round = Coordinator.TryGetRound(roundId);

            if (round == null)
            {
                returnFailureResponse = NotFound("Round not found.");
                return(null, null);
            }

            Alice alice = round.TryGetAliceBy(uniqueIdGuid);

            if (alice == null)
            {
                returnFailureResponse = NotFound("Alice not found.");
                return(round, null);
            }

            if (round.Status != CcjRoundStatus.Running)
            {
                returnFailureResponse = Gone("Round is not running.");
            }

            return(round, alice);
        }
        public IActionResult PostUncorfimation([FromQuery] string uniqueId, [FromQuery] long roundId)
        {
            if (roundId <= 0 || !ModelState.IsValid)
            {
                return(BadRequest());
            }

            Guid uniqueIdGuid = CheckUniqueId(uniqueId, out IActionResult returnFailureResponse);

            if (returnFailureResponse != null)
            {
                return(returnFailureResponse);
            }

            CcjRound round = Coordinator.TryGetRound(roundId);

            if (round == null)
            {
                return(Ok("Round not found."));
            }

            Alice alice = round.TryGetAliceBy(uniqueIdGuid);

            if (round == null)
            {
                return(Ok("Alice not found."));
            }

            if (round.Status != CcjRoundStatus.Running)
            {
                return(Forbid("Round is not running."));
            }

            CcjRoundPhase phase = round.Phase;

            switch (phase)
            {
            case CcjRoundPhase.InputRegistration:
            {
                round.RemoveAlicesBy(uniqueIdGuid);
                return(NoContent());
            }

            default:
            {
                return(Forbid($"Participation can be only unconfirmed from InputRegistration phase. Current phase: {phase}."));
            }
            }
        }
Exemplo n.º 3
0
        public IActionResult GetCoinJoin([FromQuery] string uniqueId, [FromQuery] long roundId)
        {
            if (roundId <= 0 || !ModelState.IsValid)
            {
                return(BadRequest());
            }

            Guid uniqueIdGuid = CheckUniqueId(uniqueId, out IActionResult returnFailureResponse);

            if (returnFailureResponse != null)
            {
                return(returnFailureResponse);
            }

            CcjRound round = Coordinator.TryGetRound(roundId);

            if (round == null)
            {
                return(NotFound("Round not found."));
            }

            if (round.TryGetAliceBy(uniqueIdGuid) == null)             // We don't care about Alice now, but let's not give the CJ to anyone who isn't participating.
            {
                return(NotFound("Alice not found."));
            }

            if (round.Status != CcjRoundStatus.Running)
            {
                return(Gone("Round is not running."));
            }

            CcjRoundPhase phase = round.Phase;

            switch (phase)
            {
            case CcjRoundPhase.Signing:
            {
                return(Ok(round.GetUnsignedCoinJoinHex()));
            }

            default:
            {
                return(Conflict($"CoinJoin can only be requested from Signing phase. Current phase: {phase}."));
            }
            }
        }
        public async Task <IActionResult> PostConfirmationAsync([FromQuery] string uniqueId, [FromQuery] long roundId)
        {
            if (roundId <= 0 || !ModelState.IsValid)
            {
                return(BadRequest());
            }

            Guid uniqueIdGuid = CheckUniqueId(uniqueId, out IActionResult returnFailureResponse);

            if (returnFailureResponse != null)
            {
                return(returnFailureResponse);
            }

            CcjRound round = Coordinator.TryGetRound(roundId);

            if (round == null)
            {
                return(NotFound("Round not found."));
            }

            Alice alice = round.TryGetAliceBy(uniqueIdGuid);

            if (round == null)
            {
                return(NotFound("Alice not found."));
            }

            if (round.Status != CcjRoundStatus.Running)
            {
                return(Forbid("Round is not running."));
            }

            CcjRoundPhase phase = round.Phase;

            switch (phase)
            {
            case CcjRoundPhase.InputRegistration:
            {
                round.StartAliceTimeout(uniqueIdGuid);
                return(NoContent());
            }

            case CcjRoundPhase.ConnectionConfirmation:
            {
                alice.State = AliceState.ConnectionConfirmed;

                // Progress round if needed.
                if (round.AllAlices(AliceState.ConnectionConfirmed))
                {
                    IEnumerable <Alice> alicesToBan = await round.RemoveAlicesIfInputsSpentAsync();                                    // So ban only those who confirmed participation, yet spent their inputs.

                    if (alicesToBan.Count() > 0)
                    {
                        await Coordinator.UtxoReferee.BanUtxosAsync(1, DateTimeOffset.Now, alicesToBan.SelectMany(x => x.Inputs).Select(y => y.OutPoint).ToArray());
                    }

                    int aliceCountAfterConnectionConfirmationTimeout = round.CountAlices();
                    if (aliceCountAfterConnectionConfirmationTimeout < 2)
                    {
                        round.Fail();
                    }
                    else
                    {
                        round.UpdateAnonymitySet(aliceCountAfterConnectionConfirmationTimeout);
                        // Progress to the next phase, which will be OutputRegistration
                        await round.ExecuteNextPhaseAsync(CcjRoundPhase.OutputRegistration);
                    }
                }

                return(Ok(round.RoundHash));                                // Participation can be confirmed multiple times, whatever.
            }

            default:
            {
                return(Forbid($"Participation can be only confirmed from InputRegistration or ConnectionConfirmation phase. Current phase: {phase}."));
            }
            }
        }
Exemplo n.º 5
0
        public async Task <IActionResult> PostSignaturesAsync([FromQuery] string uniqueId, [FromQuery] long roundId, [FromBody] IDictionary <int, string> signatures)
        {
            if (roundId <= 0 ||
                signatures == null ||
                signatures.Count() <= 0 ||
                signatures.Any(x => x.Key < 0 || string.IsNullOrWhiteSpace(x.Value)) ||
                !ModelState.IsValid)
            {
                return(BadRequest());
            }

            Guid uniqueIdGuid = CheckUniqueId(uniqueId, out IActionResult returnFailureResponse);

            if (returnFailureResponse != null)
            {
                return(returnFailureResponse);
            }

            CcjRound round = Coordinator.TryGetRound(roundId);

            if (round == null)
            {
                return(NotFound("Round not found."));
            }

            Alice alice = round.TryGetAliceBy(uniqueIdGuid);

            if (alice == null)
            {
                return(NotFound("Alice not found."));
            }

            // Check if Alice provided signature to all her inputs.
            if (signatures.Count != alice.Inputs.Count())
            {
                return(BadRequest("Alice did not provide enough witnesses."));
            }

            if (round.Status != CcjRoundStatus.Running)
            {
                return(Gone("Round is not running."));
            }

            CcjRoundPhase phase = round.Phase;

            switch (phase)
            {
            case CcjRoundPhase.Signing:
            {
                using (await SigningLock.LockAsync())
                {
                    foreach (var signaturePair in signatures)
                    {
                        int       index   = signaturePair.Key;
                        WitScript witness = null;
                        try
                        {
                            witness = new WitScript(signaturePair.Value);
                        }
                        catch (Exception ex)
                        {
                            return(BadRequest($"Malformed witness is provided. Details: {ex.Message}"));
                        }
                        int maxIndex = round.UnsignedCoinJoin.Inputs.Count - 1;
                        if (maxIndex < index)
                        {
                            return(BadRequest($"Index out of range. Maximum value: {maxIndex}. Provided value: {index}"));
                        }

                        // Check duplicates.
                        if (!string.IsNullOrWhiteSpace(round.SignedCoinJoin.Inputs[index].WitScript?.ToString()))                                        // Not sure why WitScript?.ToString() is needed, there was something wrong in previous HiddenWallet version if I didn't do this.
                        {
                            return(BadRequest($"Input is already signed."));
                        }

                        // Verify witness.
                        var cjCopy = new Transaction(round.UnsignedCoinJoin.ToHex());
                        cjCopy.Inputs[index].WitScript = witness;
                        TxOut output = alice.Inputs.Single(x => x.OutPoint == cjCopy.Inputs[index].PrevOut).Output;
                        if (!Script.VerifyScript(output.ScriptPubKey, cjCopy, index, output.Value, ScriptVerify.Standard, SigHash.All))
                        {
                            return(BadRequest($"Invalid witness is provided."));
                        }

                        // Finally add it to our CJ.
                        round.SignedCoinJoin.Inputs[index].WitScript = witness;
                    }

                    alice.State = AliceState.SignedCoinJoin;

                    await round.BroadcastCoinJoinIfFullySignedAsync();
                }

                return(NoContent());
            }

            default:
            {
                return(Conflict($"CoinJoin can only be requested from Signing phase. Current phase: {phase}."));
            }
            }
        }