public async Task <IActionResult> PostInputsAsync([FromBody] InputsRequest request) { // Validate request. if (!ModelState.IsValid || request == null || string.IsNullOrWhiteSpace(request.BlindedOutputScriptHex) || string.IsNullOrWhiteSpace(request.ChangeOutputAddress) || request.Inputs == null || !request.Inputs.Any() || request.Inputs.Any(x => x.Input == default(TxoRef) || x.Input.TransactionId == 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 <(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.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(); if (Coordinator.UtxoReferee.BannedUtxos.TryGetValue(outpoint, out (int severity, DateTimeOffset timeOfBan)bannedElem)) { int maxBan = (int)TimeSpan.FromDays(30).TotalMinutes; int banLeft = maxBan - (int)((DateTimeOffset.UtcNow - bannedElem.timeOfBan).TotalMinutes); if (banLeft > 0) { return(BadRequest($"Input is banned from participation for {banLeft} minutes: {inputProof.Input.Index}:{inputProof.Input.TransactionId}.")); } await Coordinator.UtxoReferee.UnbanAsync(outpoint); } GetTxOutResponse getTxOutResponse = await RpcClient.GetTxOutAsync(inputProof.Input.TransactionId, (int)inputProof.Input.Index, 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.TransactionId)) { 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.ToOutPoint(), txout)); } // Check if inputs have enough coins. Money inputSum = inputs.Select(x => x.Output.Value).Sum(); 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.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)); } } }
public async Task SignTransactionAsync() { WabiSabiConfig config = new(); Round round = WabiSabiFactory.CreateRound(config); using Key key1 = new(); Alice alice1 = WabiSabiFactory.CreateAlice(key: key1); round.Alices.Add(alice1); using Key key2 = new(); Alice alice2 = WabiSabiFactory.CreateAlice(key: key2); round.Alices.Add(alice2); using Arena arena = await WabiSabiFactory.CreateAndStartArenaAsync(config, round); var mockRpc = new Mock <IRPCClient>(); await using var coordinator = new ArenaRequestHandler(config, new Prison(), arena, mockRpc.Object); var wabiSabiApi = new WabiSabiController(coordinator); var rnd = new InsecureRandom(); ZeroCredentialPool zeroAmountCredentials = new(); ZeroCredentialPool zeroVsizeCredentials = new(); var amountClient = new WabiSabiClient(round.AmountCredentialIssuerParameters, rnd, 4300000000000ul, zeroAmountCredentials); var vsizeClient = new WabiSabiClient(round.VsizeCredentialIssuerParameters, rnd, 2000ul, zeroVsizeCredentials); var apiClient = new ArenaClient(amountClient, vsizeClient, wabiSabiApi); round.SetPhase(Phase.TransactionSigning); var emptyState = round.Assert <ConstructionState>(); // We can't use ``emptyState.Finalize()` because this is not a valid transaction so we fake it var finalizedEmptyState = new SigningState(emptyState.Parameters, emptyState.Inputs, emptyState.Outputs); // No inputs in the CoinJoin. await Assert.ThrowsAsync <ArgumentException>(async() => await apiClient.SignTransactionAsync(round.Id, alice1.Coin, new BitcoinSecret(key1, Network.Main), finalizedEmptyState.CreateUnsignedTransaction(), CancellationToken.None)); var oneInput = emptyState.AddInput(alice1.Coin).Finalize(); round.CoinjoinState = oneInput; // Trying to sign coins those are not in the CoinJoin. await Assert.ThrowsAsync <InvalidOperationException>(async() => await apiClient.SignTransactionAsync(round.Id, alice2.Coin, new BitcoinSecret(key2, Network.Main), oneInput.CreateUnsignedTransaction(), CancellationToken.None)); var twoInputs = emptyState.AddInput(alice1.Coin).AddInput(alice2.Coin).Finalize(); round.CoinjoinState = twoInputs; // Trying to sign coins with the wrong secret. await Assert.ThrowsAsync <InvalidOperationException>(async() => await apiClient.SignTransactionAsync(round.Id, alice1.Coin, new BitcoinSecret(key2, Network.Main), twoInputs.CreateUnsignedTransaction(), CancellationToken.None)); Assert.False(round.Assert <SigningState>().IsFullySigned); var unsigned = round.Assert <SigningState>().CreateUnsignedTransaction(); await apiClient.SignTransactionAsync(round.Id, alice1.Coin, new BitcoinSecret(key1, Network.Main), unsigned, CancellationToken.None); Assert.True(round.Assert <SigningState>().IsInputSigned(alice1.Coin.Outpoint)); Assert.False(round.Assert <SigningState>().IsInputSigned(alice2.Coin.Outpoint)); Assert.False(round.Assert <SigningState>().IsFullySigned); await apiClient.SignTransactionAsync(round.Id, alice2.Coin, new BitcoinSecret(key2, Network.Main), unsigned, CancellationToken.None); Assert.True(round.Assert <SigningState>().IsInputSigned(alice2.Coin.Outpoint)); Assert.True(round.Assert <SigningState>().IsFullySigned); }
protected void btnSave_Click(object sender, EventArgs e) { string share_id = Request.QueryString["share_id"]; string query = "select cloud_id, file_name,fid from Share_Master where share_id='" + share_id + "'"; DataTable dt = Database.Getdata(query); string cloud_id = Convert.ToString(dt.Rows[0]["cloud_id"]); string file_name = Convert.ToString(dt.Rows[0]["file_name"]); int fileid = Convert.ToInt32(dt.Rows[0]["fid"]); if (cloud_id == "Cloud") { SqlCommand compare_keys = new SqlCommand("CompareKeys"); var sqltype = CommandType.StoredProcedure; SqlParameter[] sqlpara = { new SqlParameter("@key_value", txtsecret_key.Text) }; DataTable key_output = Database.Getdata_Store(Database.cs, compare_keys, sqlpara, sqltype); if (key_output.Rows.Count > 0) { file_path = Convert.ToString(key_output.Rows[0]["file_path"]); key = txtsecret_key.Text; fileName = Path.GetFileNameWithoutExtension(file_path); fileExtension = Path.GetExtension(file_path); ////////decryption //output = Server.MapPath("../Files/") + fileName + "_dec" + fileExtension; output = fileName + "_dec" + fileExtension; input_file = Server.MapPath("../Files/" + file_path); SqlDataAdapter adp = new SqlDataAdapter("select * from File_Master where file_id='" + fileid + "'", Database.cs); DataTable dt1 = new DataTable(); adp.Fill(dt1); if (dt1.Rows.Count > 0) { var keyBytes = (Byte[])dt1.Rows[0]["FileKey"]; var iv = (Byte[])dt1.Rows[0]["IV"]; Byte[] fileBytes = Alice.Receive(File.ReadAllBytes(input_file), keyBytes, iv); HttpResponse req = HttpContext.Current.Response; req.AddHeader("Content-Disposition", "attachment;filename=\"" + output + "\""); req.BinaryWrite(fileBytes); req.End(); txtsecret_key.Text = ""; Response.Redirect("Download_File.aspx"); } } else { Response.Write("<script>alert('invalid keys')</script>"); } txtsecret_key.Text = ""; } else { Response.Write("<script>alert('invalid keys.')</script>"); } }
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 (alice == null) { return(NotFound("Alice not found.")); } if (round.Status != CcjRoundStatus.Running) { return(Gone("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(Gone($"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}.")); } } }
public async Task <IActionResult> SignatureAsync([FromBody] SignatureRequest request) { try { if (Global.StateMachine.Phase != TumblerPhase.Signing || !Global.StateMachine.AcceptRequest) { return(new ObjectResult(new FailureResponse { Message = "Wrong phase" })); } if (string.IsNullOrWhiteSpace(request.UniqueId)) { return(new BadRequestResult()); } if (request.Signatures == null) { return(new BadRequestResult()); } if (request.Signatures.Count() == 0) { return(new BadRequestResult()); } Alice alice = Global.StateMachine.FindAlice(request.UniqueId, throwException: true); using (await SignatureProvidedAsyncLock.LockAsync()) { Transaction coinJoin = Global.StateMachine.CoinJoin; foreach (var(Witness, Index) in request.Signatures) { var witness = new WitScript(Witness); if (coinJoin.Inputs.Count <= Index) { // round fails, ban alice await Global.UtxoReferee.BanAliceAsync(alice); Global.StateMachine.FallBackRound = true; Global.StateMachine.UpdatePhase(TumblerPhase.InputRegistration); throw new ArgumentOutOfRangeException(nameof(Index)); } coinJoin.Inputs[Index].WitScript = witness; var output = alice.Inputs.Single(x => x.OutPoint == coinJoin.Inputs[Index].PrevOut).Output; if (!Script.VerifyScript(output.ScriptPubKey, coinJoin, Index, output.Value, ScriptVerify.Standard, SigHash.All)) { // round fails, ban alice await Global.UtxoReferee.BanAliceAsync(alice); Global.StateMachine.FallBackRound = true; Global.StateMachine.UpdatePhase(TumblerPhase.InputRegistration); throw new InvalidOperationException("Invalid witness"); } else { Global.StateMachine.CoinJoin = coinJoin; } } // check if fully signed if (Global.StateMachine.FullySignedCoinJoin) { Console.WriteLine("Trying to propagate coinjoin...."); ConcurrentHashSet <Alice> alices = Global.StateMachine.Alices; Coin[] spentCoins = alices.SelectMany(x => x.Inputs.Select(y => new Coin(y.OutPoint, y.Output))).ToArray(); var fee = coinJoin.GetFee(spentCoins); Console.WriteLine($"Fee: {fee.ToString(false, true)}"); var feeRate = coinJoin.GetFeeRate(spentCoins); Console.WriteLine($"FeeRate: {feeRate.FeePerK.ToDecimal(MoneyUnit.Satoshi) / 1000} satoshi/byte"); var state = CoinJoinTransactionState.Failed; try { var res = await Global.RpcClient.SendCommandAsync(RPCOperations.sendrawtransaction, coinJoin.ToHex(), true); if (coinJoin.GetHash().ToString() == res.ResultString) { state = CoinJoinTransactionState.Succeeded; Console.WriteLine($"Propagated transaction: {coinJoin.GetHash()}"); } } finally { if (state == CoinJoinTransactionState.Failed) { Global.StateMachine.FallBackRound = true; } else { Global.StateMachine.FallBackRound = false; } Global.CoinJoinStore.Transactions.Add(new CoinJoinTransaction { Transaction = coinJoin, DateTime = DateTimeOffset.UtcNow, State = state }); await Global.CoinJoinStore.ToFileAsync(Global.CoinJoinStorePath); Global.StateMachine.UpdatePhase(TumblerPhase.InputRegistration); } } alice.State = AliceState.SignedCoinJoin; return(new ObjectResult(new SuccessResponse())); } } catch (Exception ex) { return(new ObjectResult(new FailureResponse { Message = ex.Message })); } }
public static InputsRegistrationResponse RegisterInput( WabiSabiConfig config, Guid roundId, IDictionary <Coin, byte[]> coinRoundSignaturePairs, ZeroCredentialsRequest zeroAmountCredentialRequests, ZeroCredentialsRequest zeroWeightCredentialRequests, IDictionary <Guid, Round> rounds, Network network) { if (!rounds.TryGetValue(roundId, out var round)) { throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.RoundNotFound); } var alice = new Alice(coinRoundSignaturePairs); var coins = alice.Coins; if (round.MaxInputCountByAlice < coins.Count()) { throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.TooManyInputs); } if (round.IsBlameRound && coins.Select(x => x.Outpoint).Any(x => !round.BlameWhitelist.Contains(x))) { throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.InputNotWhitelisted); } var inputValueSum = Money.Zero; var inputWeightSum = 0; foreach (var coinRoundSignaturePair in alice.CoinRoundSignaturePairs) { var coin = coinRoundSignaturePair.Key; var signature = coinRoundSignaturePair.Value; var coinJoinInputCommitmentData = new CoinJoinInputCommitmentData("CoinJoinCoordinatorIdentifier", round.Hash); if (!OwnershipProof.VerifyCoinJoinInputProof(signature, coin.TxOut.ScriptPubKey, coinJoinInputCommitmentData)) { throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.WrongRoundSignature); } inputValueSum += coin.TxOut.Value; // Convert conservative P2WPKH size in virtual bytes to weight units. inputWeightSum += Constants.WitnessScaleFactor * coin.TxOut.ScriptPubKey.EstimateInputVsize(); } if (inputValueSum < round.MinRegistrableAmount) { throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.NotEnoughFunds); } if (inputValueSum > round.MaxRegistrableAmount) { throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.TooMuchFunds); } if (inputWeightSum > round.RegistrableWeightCredentials) { throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.TooMuchWeight); } if (round.IsInputRegistrationEnded(config.MaxInputCountByRound, config.GetInputRegistrationTimeout(round))) { throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.WrongPhase); } var commitAmountCredentialResponse = round.AmountCredentialIssuer.PrepareResponse(zeroAmountCredentialRequests); var commitWeightCredentialResponse = round.WeightCredentialIssuer.PrepareResponse(zeroWeightCredentialRequests); RemoveDuplicateAlices(rounds, alice); alice.SetDeadlineRelativeTo(round.ConnectionConfirmationTimeout); round.Alices.Add(alice); return(new(alice.Id, commitAmountCredentialResponse(), commitWeightCredentialResponse())); }
public static InputsRegistrationResponse RegisterInput( WabiSabiConfig config, Guid roundId, IDictionary <Coin, byte[]> coinRoundSignaturePairs, ZeroCredentialsRequest zeroAmountCredentialRequests, ZeroCredentialsRequest zeroVsizeCredentialRequests, IDictionary <Guid, Round> rounds, Network network) { if (!rounds.TryGetValue(roundId, out var round)) { throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.RoundNotFound); } var coins = coinRoundSignaturePairs.Select(x => x.Key); if (round.MaxInputCountByAlice < coins.Count()) { throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.TooManyInputs); } if (round.IsBlameRound && coins.Select(x => x.Outpoint).Any(x => !round.BlameWhitelist.Contains(x))) { throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.InputNotWhitelisted); } foreach (var coinRoundSignaturePair in coinRoundSignaturePairs) { var coin = coinRoundSignaturePair.Key; var signature = coinRoundSignaturePair.Value; var coinJoinInputCommitmentData = new CoinJoinInputCommitmentData("CoinJoinCoordinatorIdentifier", round.Hash); if (!OwnershipProof.VerifyCoinJoinInputProof(signature, coin.TxOut.ScriptPubKey, coinJoinInputCommitmentData)) { throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.WrongRoundSignature); } } var alice = new Alice(coinRoundSignaturePairs); if (alice.TotalInputAmount < round.MinRegistrableAmount) { throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.NotEnoughFunds); } if (alice.TotalInputAmount > round.MaxRegistrableAmount) { throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.TooMuchFunds); } if (alice.TotalInputVsize > round.PerAliceVsizeAllocation) { throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.TooMuchVsize); } if (round.IsInputRegistrationEnded(config.MaxInputCountByRound, config.GetInputRegistrationTimeout(round))) { throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.WrongPhase); } if (round.RemainingInputVsizeAllocation < round.PerAliceVsizeAllocation) { throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.VsizeQuotaExceeded); } var commitAmountCredentialResponse = round.AmountCredentialIssuer.PrepareResponse(zeroAmountCredentialRequests); var commitVsizeCredentialResponse = round.VsizeCredentialIssuer.PrepareResponse(zeroVsizeCredentialRequests); RemoveDuplicateAlices(rounds, alice); alice.SetDeadlineRelativeTo(round.ConnectionConfirmationTimeout); round.Alices.Add(alice); return(new(alice.Id, commitAmountCredentialResponse.Commit(), commitVsizeCredentialResponse.Commit())); }
public void PublicSendMessageToAlice() { Alice.Create().FriendRecieveMessageFromBob <BobPrivateKey>(1337); }
public Bob(Alice alice) { this.alice = alice; thread = new Thread(Loop); thread.Start(); }
private static void RemoveDuplicateAlices(IDictionary <Guid, Round> roundsWithId, Alice alice) { var rounds = roundsWithId.Values; if (rounds.Any(x => x.Phase != Phase.InputRegistration)) { throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.AliceAlreadyRegistered); } var aliceOutPoints = alice.Coins.Select(x => x.Outpoint).ToHashSet(); var flattenTable = rounds.SelectMany(x => x.Alices.SelectMany(y => y.Coins.Select(z => (Round: x, Alice: y, Output: z.Outpoint)))); foreach (var(round, aliceInRound, _) in flattenTable.Where(x => aliceOutPoints.Contains(x.Output)).ToArray()) { if (round.Alices.Remove(aliceInRound)) { Logger.LogInfo("Updated Alice registration."); } } }
public static InputRegistrationResponse RegisterInput( WabiSabiConfig config, uint256 roundId, Coin coin, OwnershipProof ownershipProof, ZeroCredentialsRequest zeroAmountCredentialRequests, ZeroCredentialsRequest zeroVsizeCredentialRequests, HashSet <Round> rounds) { if (rounds.FirstOrDefault(x => x.Id == roundId) is not Round round) { throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.RoundNotFound); } if (round.IsInputRegistrationEnded(config.MaxInputCountByRound, config.GetInputRegistrationTimeout(round))) { throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.WrongPhase); } if (round.IsBlameRound && !round.BlameWhitelist.Contains(coin.Outpoint)) { throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.InputNotWhitelisted); } // Compute but don't commit updated CoinJoin to round state, it will // be re-calculated on input confirmation. This is computed it here // for validation purposes. round.Assert <ConstructionState>().AddInput(coin); var coinJoinInputCommitmentData = new CoinJoinInputCommitmentData("CoinJoinCoordinatorIdentifier", round.Id); if (!OwnershipProof.VerifyCoinJoinInputProof(ownershipProof, coin.TxOut.ScriptPubKey, coinJoinInputCommitmentData)) { throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.WrongOwnershipProof); } var alice = new Alice(coin, ownershipProof, round); if (alice.TotalInputAmount < round.MinRegistrableAmount) { throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.NotEnoughFunds); } if (alice.TotalInputAmount > round.MaxRegistrableAmount) { throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.TooMuchFunds); } if (alice.TotalInputVsize > round.MaxVsizeAllocationPerAlice) { throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.TooMuchVsize); } if (round.RemainingInputVsizeAllocation < round.MaxVsizeAllocationPerAlice) { throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.VsizeQuotaExceeded); } var commitAmountCredentialResponse = round.AmountCredentialIssuer.PrepareResponse(zeroAmountCredentialRequests); var commitVsizeCredentialResponse = round.VsizeCredentialIssuer.PrepareResponse(zeroVsizeCredentialRequests); alice.SetDeadlineRelativeTo(round.ConnectionConfirmationTimeout); round.Alices.Add(alice); return(new(alice.Id, commitAmountCredentialResponse.Commit(), commitVsizeCredentialResponse.Commit())); }
public void Ban(Alice alice, uint256 lastDisruptedRoundId) { Punish(alice.Coin.Outpoint, Punishment.Banned, lastDisruptedRoundId); }
public async Task <IActionResult> InputsAsync([FromBody] InputsRequest request) { var roundId = Global.StateMachine.RoundId; TumblerPhase phase = TumblerPhase.InputRegistration; try { if (Global.StateMachine.Phase != TumblerPhase.InputRegistration || !Global.StateMachine.AcceptRequest) { return(new ObjectResult(new FailureResponse { Message = "Wrong phase" })); } // Check not nulls string blindedOutputString = request.BlindedOutput.Trim(); if (string.IsNullOrWhiteSpace(blindedOutputString)) { return(new BadRequestResult()); } if (string.IsNullOrWhiteSpace(request.ChangeOutput)) { return(new BadRequestResult()); } if (request.Inputs == null || request.Inputs.Count() == 0) { return(new BadRequestResult()); } // Check format (parse everyting)) if (Global.StateMachine.BlindedOutputs.Contains(blindedOutputString)) { throw new ArgumentException("Blinded output has already been registered"); } byte[] blindedOutput = HexHelpers.GetBytes(blindedOutputString); Network network = Global.Config.Network; var changeOutput = new BitcoinWitPubKeyAddress(request.ChangeOutput, expectedNetwork: network); if (request.Inputs.Count() > Global.Config.MaximumInputsPerAlices) { throw new NotSupportedException("Too many inputs provided"); } var inputs = new HashSet <(TxOut Output, OutPoint OutPoint)>(); var alicesToRemove = new HashSet <Guid>(); using (await InputRegistrationLock.LockAsync()) { foreach (InputProofModel input in request.Inputs) { var op = new OutPoint(); op.FromHex(input.Input); if (inputs.Any(x => x.OutPoint.Hash == op.Hash && x.OutPoint.N == op.N)) { throw new ArgumentException("Attempting to register an input twice is not permitted"); } foreach (var a in Global.StateMachine.Alices) { if (a.Inputs.Any(x => x.OutPoint.Hash == op.Hash && x.OutPoint.N == op.N)) { alicesToRemove.Add(a.UniqueId); // input is already registered by this alice, remove it if all the checks are completed fine } } BannedUtxo banned = Global.UtxoReferee.Utxos.FirstOrDefault(x => x.Utxo.Hash == op.Hash && x.Utxo.N == op.N); if (banned != default(BannedUtxo)) { var maxBan = (int)TimeSpan.FromDays(30).TotalMinutes; int banLeft = maxBan - (int)((DateTimeOffset.UtcNow - banned.TimeOfBan).TotalMinutes); throw new ArgumentException($"Input is banned for {banLeft} minutes"); } var getTxOutResponse = await Global.RpcClient.GetTxOutAsync(op.Hash, (int)op.N, true); // Check if inputs are unspent if (getTxOutResponse == null) { throw new ArgumentException("Provided input is not unspent"); } // Check if inputs are unconfirmed, if so check if they are part of previous CoinJoin if (getTxOutResponse.Confirmations <= 0) { if (!Global.CoinJoinStore.Transactions .Any(x => x.State >= CoinJoinTransactionState.Succeeded && x.Transaction.GetHash() == op.Hash)) { throw new ArgumentException("Provided input is not confirmed, nor spends a previous CJ transaction"); } else { // after 24 unconfirmed cj in the mempool dont't let unconfirmed coinjoin to be registered var unconfirmedCoinJoins = Global.CoinJoinStore.Transactions.Where(x => x.State == CoinJoinTransactionState.Succeeded); if (unconfirmedCoinJoins.Count() >= 24) { var toFailed = new HashSet <uint256>(); var toConfirmed = new HashSet <uint256>(); foreach (var tx in unconfirmedCoinJoins) { RPCResponse getRawTransactionResponse = (await Global.RpcClient.SendCommandAsync("getrawtransaction", tx.Transaction.GetHash().ToString(), true)); if (string.IsNullOrWhiteSpace(getRawTransactionResponse?.ResultString)) { toFailed.Add(tx.Transaction.GetHash()); } if (getRawTransactionResponse.Result.Value <int>("confirmations") > 0) { toConfirmed.Add(tx.Transaction.GetHash()); } } foreach (var tx in toFailed) { Global.CoinJoinStore.TryUpdateState(tx, CoinJoinTransactionState.Failed); } foreach (var tx in toConfirmed) { Global.CoinJoinStore.TryUpdateState(tx, CoinJoinTransactionState.Confirmed); } if (toFailed.Count + toConfirmed.Count > 0) { await Global.CoinJoinStore.ToFileAsync(Global.CoinJoinStorePath); } // if couldn't remove any unconfirmed tx then refuse registration if (Global.CoinJoinStore.Transactions.Count(x => x.State == CoinJoinTransactionState.Succeeded) >= 24) { throw new ArgumentException("Registering unconfirmed CJ transaction output is currently not allowed due to too long mempool chain"); } } } } // Check coinbase > 100 if (getTxOutResponse.Confirmations < 100) { if (getTxOutResponse.IsCoinBase) { throw new ArgumentException("Provided input is unspendable"); } } // Check if inputs are native segwit if (getTxOutResponse.ScriptPubKeyType != "witness_v0_keyhash") { throw new ArgumentException("Provided input is not witness_v0_keyhash"); } var txout = getTxOutResponse.TxOut; var address = (BitcoinWitPubKeyAddress)txout.ScriptPubKey.GetDestinationAddress(network); // Check if proofs are valid var validProof = address.VerifyMessage(blindedOutputString, input.Proof); if (!validProof) { throw new ArgumentException("Provided proof is invalid"); } inputs.Add((txout, op)); } // Check if inputs have enough coins Money amount = Money.Zero; foreach (Money val in inputs.Select(x => x.Output.Value)) { amount += val; } Money feeToPay = (inputs.Count() * Global.StateMachine.FeePerInputs + 2 * Global.StateMachine.FeePerOutputs); Money changeAmount = amount - (Global.StateMachine.Denomination + feeToPay); if (changeAmount < Money.Zero + new Money(548)) // 546 is dust { throw new ArgumentException("Total provided inputs must be > denomination + fee + dust"); } byte[] signature = Global.RsaKey.SignBlindedData(blindedOutput); Global.StateMachine.BlindedOutputs.Add(blindedOutputString); Guid uniqueId = Guid.NewGuid(); var alice = new Alice { UniqueId = uniqueId, ChangeOutput = changeOutput, ChangeAmount = changeAmount, State = AliceState.InputsRegistered }; alice.Inputs = new ConcurrentHashSet <(TxOut Output, OutPoint OutPoint)>(); foreach (var input in inputs) { alice.Inputs.Add(input); } AssertPhase(roundId, phase); foreach (var aliceToRemove in alicesToRemove) { if (Global.StateMachine.TryRemoveAlice(aliceToRemove)) { await Global.StateMachine.BroadcastPeerRegisteredAsync(); } } Global.StateMachine.Alices.Add(alice); await Global.StateMachine.BroadcastPeerRegisteredAsync(); if (Global.StateMachine.Alices.Count >= Global.StateMachine.AnonymitySet) { Global.StateMachine.UpdatePhase(TumblerPhase.ConnectionConfirmation); } TumblerStateMachine.EstimateInputAndOutputSizes(out int inputSizeInBytes, out int outputSizeInBytes); int estimatedTxSize = Global.StateMachine.Alices.SelectMany(x => x.Inputs).Count() * inputSizeInBytes + 2 * outputSizeInBytes; if (estimatedTxSize >= 90000) // standard transaction is < 100KB { Global.StateMachine.UpdatePhase(TumblerPhase.ConnectionConfirmation); } var ret = new ObjectResult(new InputsResponse() { UniqueId = uniqueId.ToString(), SignedBlindedOutput = HexHelpers.ToString(signature) }); return(ret); } } catch (Exception ex) { return(new ObjectResult(new FailureResponse { Message = ex.Message })); } }
private async Task <InputRegistrationResponse> RegisterInputCoreAsync(InputRegistrationRequest request, CancellationToken cancellationToken) { var coin = await OutpointToCoinAsync(request, cancellationToken).ConfigureAwait(false); using (await AsyncLock.LockAsync(cancellationToken).ConfigureAwait(false)) { var round = GetRound(request.RoundId); var registeredCoins = Rounds.Where(x => !(x.Phase == Phase.Ended && !x.WasTransactionBroadcast)) .SelectMany(r => r.Alices.Select(a => a.Coin)); if (registeredCoins.Any(x => x.Outpoint == coin.Outpoint)) { throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.AliceAlreadyRegistered); } if (round.IsInputRegistrationEnded(Config.MaxInputCountByRound)) { throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.WrongPhase); } if (round is BlameRound blameRound && !blameRound.BlameWhitelist.Contains(coin.Outpoint)) { throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.InputNotWhitelisted); } // Compute but don't commit updated coinjoin to round state, it will // be re-calculated on input confirmation. This is computed in here // for validation purposes. _ = round.Assert <ConstructionState>().AddInput(coin); var coinJoinInputCommitmentData = new CoinJoinInputCommitmentData("CoinJoinCoordinatorIdentifier", round.Id); if (!OwnershipProof.VerifyCoinJoinInputProof(request.OwnershipProof, coin.TxOut.ScriptPubKey, coinJoinInputCommitmentData)) { throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.WrongOwnershipProof); } // Generate a new GUID with the secure random source, to be sure // that it is not guessable (Guid.NewGuid() documentation does // not say anything about GUID version or randomness source, // only that the probability of duplicates is very low). var id = new Guid(Random.GetBytes(16)); var isPayingZeroCoordinationFee = InMemoryCoinJoinIdStore.Contains(coin.Outpoint.Hash); if (!isPayingZeroCoordinationFee) { // If the coin comes from a tx that all of the tx inputs are coming from a CJ (1 hop - no pay). Transaction tx = await Rpc.GetRawTransactionAsync(coin.Outpoint.Hash, true, cancellationToken).ConfigureAwait(false); if (tx.Inputs.All(input => InMemoryCoinJoinIdStore.Contains(input.PrevOut.Hash))) { isPayingZeroCoordinationFee = true; } } var alice = new Alice(coin, request.OwnershipProof, round, id, isPayingZeroCoordinationFee); if (alice.CalculateRemainingAmountCredentials(round.FeeRate, round.CoordinationFeeRate) <= Money.Zero) { throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.UneconomicalInput); } if (alice.TotalInputAmount < round.MinAmountCredentialValue) { throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.NotEnoughFunds); } if (alice.TotalInputAmount > round.MaxAmountCredentialValue) { throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.TooMuchFunds); } if (alice.TotalInputVsize > round.MaxVsizeAllocationPerAlice) { throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.TooMuchVsize); } var amountCredentialTask = round.AmountCredentialIssuer.HandleRequestAsync(request.ZeroAmountCredentialRequests, cancellationToken); var vsizeCredentialTask = round.VsizeCredentialIssuer.HandleRequestAsync(request.ZeroVsizeCredentialRequests, cancellationToken); if (round.RemainingInputVsizeAllocation < round.MaxVsizeAllocationPerAlice) { throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.VsizeQuotaExceeded); } var commitAmountCredentialResponse = await amountCredentialTask.ConfigureAwait(false); var commitVsizeCredentialResponse = await vsizeCredentialTask.ConfigureAwait(false); alice.SetDeadlineRelativeTo(round.ConnectionConfirmationTimeFrame.Duration); round.Alices.Add(alice); return(new(alice.Id, commitAmountCredentialResponse, commitVsizeCredentialResponse, alice.IsPayingZeroCoordinationFee)); } }
static void Main(string[] args) { Alice.Create().PublicSendMessageToBob(); Bob.Create().PublicSendMessageToAlice(); }
public async Task <IActionResult> PostInputsAsync([FromBody, Required] InputsRequest4 request) { // Validate request. if (request.RoundId < 0) { return(BadRequest("Invalid request.")); } if (request.Inputs.Count() > 7) { return(BadRequest("Maximum 7 inputs can be registered.")); } using (await InputsLock.LockAsync()) { CoordinatorRound round = Coordinator.TryGetRound(request.RoundId); if (round is null || round.Phase != RoundPhase.InputRegistration) { return(NotFound("No such running round in InputRegistration. Try another round.")); } // Do more checks. try { var blindedOutputs = request.BlindedOutputScripts.ToArray(); int blindedOutputCount = blindedOutputs.Length; int maxBlindedOutputCount = round.MixingLevels.Count(); if (blindedOutputCount > maxBlindedOutputCount) { return(BadRequest($"Too many blinded output was provided: {blindedOutputCount}, maximum: {maxBlindedOutputCount}.")); } if (blindedOutputs.Distinct().Count() < blindedOutputs.Length) { return(BadRequest("Duplicate blinded output found.")); } if (round.ContainsAnyBlindedOutputScript(blindedOutputs.Select(x => x.BlindedOutput))) { return(BadRequest("Blinded output has already been registered.")); } if (request.ChangeOutputAddress.Network != Network) { // RegTest and TestNet address formats are sometimes the same. if (Network == Network.Main) { return(BadRequest($"Invalid ChangeOutputAddress Network.")); } } var uniqueInputs = new HashSet <OutPoint>(); foreach (InputProofModel inputProof in request.Inputs) { var outpoint = inputProof.Input; if (uniqueInputs.Contains(outpoint)) { return(BadRequest("Cannot register an input twice.")); } uniqueInputs.Add(outpoint); } var alicesToRemove = new HashSet <Guid>(); var getTxOutResponses = new List <(InputProofModel inputModel, Task <GetTxOutResponse> getTxOutTask)>(); var batch = RpcClient.PrepareBatch(); foreach (InputProofModel inputProof in request.Inputs) { 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.")); } } OutPoint outpoint = inputProof.Input; var bannedElem = await Coordinator.UtxoReferee.TryGetBannedAsync(outpoint, notedToo : false); if (bannedElem is { }) { return(BadRequest($"Input is banned from participation for {(int)bannedElem.BannedRemaining.TotalMinutes} minutes: {inputProof.Input.N}:{inputProof.Input.Hash}.")); } var txOutResponseTask = batch.GetTxOutAsync(inputProof.Input.Hash, (int)inputProof.Input.N, includeMempool: true); getTxOutResponses.Add((inputProof, txOutResponseTask)); } // Perform all RPC request at once await batch.SendBatchAsync(); byte[] blindedOutputScriptHashesByte = ByteHelpers.Combine(blindedOutputs.Select(x => x.BlindedOutput.ToBytes())); uint256 blindedOutputScriptsHash = new uint256(Hashes.SHA256(blindedOutputScriptHashesByte)); var inputs = new HashSet <Coin>(); var allInputsConfirmed = true; foreach (var responses in getTxOutResponses) { var(inputProof, getTxOutResponseTask) = responses; var getTxOutResponse = await getTxOutResponseTask; // Check if inputs are unspent. if (getTxOutResponse is null) { return(BadRequest($"Provided input is not unspent: {inputProof.Input.N}:{inputProof.Input.Hash}.")); } // Check if unconfirmed. if (getTxOutResponse.Confirmations <= 0) { return(BadRequest("Provided input is unconfirmed.")); } // Check if immature. if (getTxOutResponse.IsCoinBase && getTxOutResponse.Confirmations <= 100) { 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. if (!address.VerifyMessage(blindedOutputScriptsHash, inputProof.Proof)) { return(BadRequest("Provided proof is invalid.")); } inputs.Add(new Coin(inputProof.Input, txOut)); } if (!allInputsConfirmed) { // Check if mempool would accept a fake transaction created with the registered inputs. // Fake outputs: mixlevels + 1 maximum, +1 because there can be a change. var result = await RpcClient.TestMempoolAcceptAsync(inputs, fakeOutputCount : round.MixingLevels.Count() + 1, round.FeePerInputs, round.FeePerOutputs); if (!result.accept) { return(BadRequest($"Provided input is from an unconfirmed coinjoin, but a limit is reached: {result.rejectReason}")); } } var acceptedBlindedOutputScripts = new List <BlindedOutputWithNonceIndex>(); // Calculate expected networkfee to pay after base denomination. int inputCount = inputs.Count; Money networkFeeToPayAfterBaseDenomination = (inputCount * round.FeePerInputs) + (2 * round.FeePerOutputs); // Check if inputs have enough coins. Money inputSum = inputs.Sum(x => x.Amount); Money changeAmount = (inputSum - (round.MixingLevels.GetBaseDenomination() + networkFeeToPayAfterBaseDenomination)); if (changeAmount < Money.Zero) { return(BadRequest($"Not enough inputs are provided. Fee to pay: {networkFeeToPayAfterBaseDenomination.ToString(false, true)} BTC. Round denomination: {round.MixingLevels.GetBaseDenomination().ToString(false, true)} BTC. Only provided: {inputSum.ToString(false, true)} BTC.")); } acceptedBlindedOutputScripts.Add(blindedOutputs.First()); Money networkFeeToPay = networkFeeToPayAfterBaseDenomination; // Make sure we sign the proper number of additional blinded outputs. var moneySoFar = Money.Zero; for (int i = 1; i < blindedOutputCount; i++) { if (!round.MixingLevels.TryGetDenomination(i, out Money denomination)) { break; } Money coordinatorFee = denomination.Percentage(round.CoordinatorFeePercent * round.AnonymitySet); // It should be the number of bobs, but we must make sure they'd have money to pay all. changeAmount -= (denomination + round.FeePerOutputs + coordinatorFee); networkFeeToPay += round.FeePerOutputs; if (changeAmount < Money.Zero) { break; } acceptedBlindedOutputScripts.Add(blindedOutputs[i]); } // Make sure Alice checks work. var alice = new Alice(inputs, networkFeeToPayAfterBaseDenomination, request.ChangeOutputAddress, acceptedBlindedOutputScripts.Select(x => x.BlindedOutput)); foreach (Guid aliceToRemove in alicesToRemove) { round.RemoveAlicesBy(aliceToRemove); } round.AddAlice(alice); // All checks are good. Sign. var blindSignatures = new List <uint256>(); for (int i = 0; i < acceptedBlindedOutputScripts.Count; i++) { var blindedOutput = acceptedBlindedOutputScripts[i]; var signer = round.MixingLevels.GetLevel(i).Signer; uint256 blindSignature = signer.Sign(blindedOutput.BlindedOutput, round.NonceProvider.GetNonceKeyForIndex(blindedOutput.N)); blindSignatures.Add(blindSignature); } alice.BlindedOutputSignatures = blindSignatures.ToArray(); // Check if phase changed since. if (round.Status != CoordinatorRoundStatus.Running || round.Phase != RoundPhase.InputRegistration) { return(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(RoundPhase.ConnectionConfirmation); } } var resp = new InputsResponse { UniqueId = alice.UniqueId, RoundId = round.RoundId }; return(Ok(resp)); }
public void Ban(Alice alice, uint256 lastDisruptedRoundId, bool isLongBan = false) { Punish(alice.Coin.Outpoint, Punishment.Banned, lastDisruptedRoundId, isLongBan); }