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}.")); } } }
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}.")); } } }
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}.")); } } }