public async Task <IActionResult> PostOutputAsync([FromQuery] string roundHash, [FromBody] OutputRequest request) { if (string.IsNullOrWhiteSpace(roundHash) || request is null || string.IsNullOrWhiteSpace(request.OutputAddress) || string.IsNullOrWhiteSpace(request.SignatureHex) || !ModelState.IsValid) { return(BadRequest()); } CcjRound round = Coordinator.TryGetRound(roundHash); if (round is null) { return(NotFound("Round not found.")); } if (round.Status != CcjRoundStatus.Running) { return(Gone("Round is not running.")); } CcjRoundPhase phase = round.Phase; if (phase != CcjRoundPhase.OutputRegistration) { return(Conflict($"Output registration can only be done from OutputRegistration phase. Current phase: {phase}.")); } BitcoinAddress outputAddress; try { outputAddress = BitcoinAddress.Create(request.OutputAddress, Network); } catch (FormatException ex) { return(BadRequest($"Invalid OutputAddress. Details: {ex.Message}")); } if (RsaKey.PubKey.Verify(ByteHelpers.FromHex(request.SignatureHex), outputAddress.ScriptPubKey.ToBytes())) { using (await OutputLock.LockAsync()) { Bob bob = null; try { bob = new Bob(outputAddress); round.AddBob(bob); } catch (Exception ex) { return(BadRequest($"Invalid outputAddress is provided. Details: {ex.Message}")); } if (round.CountBobs() == round.AnonymitySet) { await round.ExecuteNextPhaseAsync(CcjRoundPhase.Signing); } } return(NoContent()); } return(BadRequest("Invalid signature provided.")); }
public async Task <IActionResult> PostSignaturesAsync([FromQuery] string uniqueId, [FromQuery] long roundId, [FromBody] IDictionary <int, string> signatures) { if (roundId <= 0 || signatures is null || !signatures.Any() || signatures.Any(x => x.Key < 0 || string.IsNullOrWhiteSpace(x.Value)) || !ModelState.IsValid) { return(BadRequest()); } (CcjRound round, Alice alice) = GetRunningRoundAndAliceOrFailureResponse(roundId, uniqueId, out IActionResult returnFailureResponse); if (!(returnFailureResponse is null)) { return(returnFailureResponse); } // Check if Alice provided signature to all her inputs. if (signatures.Count != alice.Inputs.Count()) { return(BadRequest("Alice did not provide enough witnesses.")); } 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 (round.SignedCoinJoin.Inputs[index].HasWitScript()) { return(BadRequest($"Input is already signed.")); } // Verify witness. // 1. Copy UnsignedCoinJoin. Transaction cjCopy = Transaction.Parse(round.UnsignedCoinJoin.ToHex(), Network); // 2. Sign the copy. cjCopy.Inputs[index].WitScript = witness; // 3. Convert the current input to IndexedTxIn. IndexedTxIn currentIndexedInput = cjCopy.Inputs.AsIndexedInputs().Skip(index).First(); // 4. Find the corresponding registered input. Coin registeredCoin = alice.Inputs.Single(x => x.Outpoint == cjCopy.Inputs[index].PrevOut); // 5. Verify if currentIndexedInput is correctly signed, if not, return the specific error. if (!currentIndexedInput.VerifyScript(registeredCoin, out ScriptError error)) { return(BadRequest($"Invalid witness is provided. ScriptError: {error}.")); } // 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}.")); } } }
public async Task <IActionResult> PostSignaturesAsync([FromQuery] string uniqueId, [FromQuery] long roundId, [FromBody] IDictionary <int, string> signatures) { if (roundId <= 0 || signatures == null || !signatures.Any() || signatures.Any(x => x.Key < 0 || string.IsNullOrWhiteSpace(x.Value)) || !ModelState.IsValid) { return(BadRequest()); } (CcjRound round, Alice alice) = GetRunningRoundAndAliceOrFailureResponse(roundId, uniqueId, out IActionResult returnFailureResponse); if (returnFailureResponse != null) { return(returnFailureResponse); } // Check if Alice provided signature to all her inputs. if (signatures.Count != alice.Inputs.Count()) { return(BadRequest("Alice did not provide enough witnesses.")); } 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 (round.SignedCoinJoin.Inputs[index].HasWitness()) { return(BadRequest($"Input is already signed.")); } // Verify witness. var cjCopy = RpcClient.Network.Consensus.ConsensusFactory.CreateTransaction(); cjCopy.FromHex(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}.")); } } }