Make the round a pure container class
MODIFY
WalletWasabi/WabiSabi/Backend/Rounds/Arena.cs
WalletWasabi/WabiSabi/Backend/Rounds/Arena.cs
@@ -25,18 +25,13 @@ namespace WalletWasabi.WabiSabi.Backend.Rounds
private object Lock { get; } = new();
public Network Network { get; }
- public bool TryGetRound(Guid roundId, [NotNullWhen(true)] out Round? round)
+ protected override Task ActionAsync(CancellationToken cancel)
{
lock (Lock)
- {
- return Rounds.TryGetValue(roundId, out round);
- }
- }
-
- protected override Task ActionAsync(CancellationToken cancel)
{
return Task.CompletedTask;
}
+ }
public InputsRegistrationResponse RegisterInput(
Guid roundId,
@@ -44,7 +39,9 @@ namespace WalletWasabi.WabiSabi.Backend.Rounds
ZeroCredentialsRequest zeroAmountCredentialRequests,
ZeroCredentialsRequest zeroWeightCredentialRequests)
{
- if (!TryGetRound(roundId, out var round))
+ lock (Lock)
+ {
+ if (!Rounds.TryGetValue(roundId, out var round))
{
throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.RoundNotFound);
}
@@ -92,14 +89,16 @@ namespace WalletWasabi.WabiSabi.Backend.Rounds
throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.TooMuchWeight);
}
- var resp = round.RegisterAlice(
- alice,
- zeroAmountCredentialRequests,
- zeroWeightCredentialRequests);
-
- lock (Lock)
+ if (round.Phase != Phase.InputRegistration)
{
- foreach (var otherRound in Rounds.Where(x => x.Key != round.Id).Select(x => x.Value))
+ throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.WrongPhase);
+ }
+
+ var amountCredentialResponse = round.AmountCredentialIssuer.HandleRequest(zeroAmountCredentialRequests);
+ var weightCredentialResponse = round.WeightCredentialIssuer.HandleRequest(zeroWeightCredentialRequests);
+
+ alice.SetDeadlineRelativeTo(round.ConnectionConfirmationTimeout);
+ foreach (var otherRound in Rounds.Values)
{
foreach (var op in alice.Coins.Select(x => x.Outpoint))
{
@@ -116,33 +115,85 @@ namespace WalletWasabi.WabiSabi.Backend.Rounds
}
}
}
- }
- return resp;
+ round.Alices.Add(alice);
+
+ return new(alice.Id, amountCredentialResponse, weightCredentialResponse);
+ }
}
public void RemoveInput(InputsRemovalRequest request)
{
- if (!TryGetRound(request.RoundId, out var round))
+ lock (Lock)
+ {
+ if (!Rounds.TryGetValue(request.RoundId, out var round))
{
throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.RoundNotFound);
}
round.RemoveAlices(x => x.Id == request.AliceId);
}
+ }
public ConnectionConfirmationResponse ConfirmConnection(ConnectionConfirmationRequest request)
{
- if (!TryGetRound(request.RoundId, out var round))
+ lock (Lock)
+ {
+ if (!Rounds.TryGetValue(request.RoundId, out var round))
{
throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.RoundNotFound);
}
- return round.ConfirmAlice(request);
+ var alice = round.Alices.FirstOrDefault(x => x.Id == request.AliceId);
+ if (alice is null)
+ {
+ throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.AliceNotFound);
+ }
+
+ var amountZeroCredentialResponse = round.AmountCredentialIssuer.HandleRequest(request.ZeroAmountCredentialRequests);
+ var weightZeroCredentialResponse = round.WeightCredentialIssuer.HandleRequest(request.ZeroWeightCredentialRequests);
+
+ var realAmountCredentialRequests = request.RealAmountCredentialRequests;
+ var realWeightCredentialRequests = request.RealWeightCredentialRequests;
+
+ if (realWeightCredentialRequests.Delta != alice.CalculateRemainingWeightCredentials(round.RegistrableWeightCredentials))
+ {
+ throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.IncorrectRequestedWeightCredentials);
+ }
+ if (realAmountCredentialRequests.Delta != alice.CalculateRemainingAmountCredentials(round.FeeRate))
+ {
+ throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.IncorrectRequestedAmountCredentials);
+ }
+
+ if (round.Phase == Phase.InputRegistration)
+ {
+ alice.SetDeadlineRelativeTo(round.ConnectionConfirmationTimeout);
+ return new(
+ amountZeroCredentialResponse,
+ weightZeroCredentialResponse);
+ }
+ else if (round.Phase == Phase.ConnectionConfirmation)
+ {
+ var amountRealCredentialResponse = round.AmountCredentialIssuer.HandleRequest(realAmountCredentialRequests);
+ var weightRealCredentialResponse = round.WeightCredentialIssuer.HandleRequest(realWeightCredentialRequests);
+
+ return new(
+ amountZeroCredentialResponse,
+ weightZeroCredentialResponse,
+ amountRealCredentialResponse,
+ weightRealCredentialResponse);
+ }
+ else
+ {
+ throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.WrongPhase);
+ }
+ }
}
public OutputRegistrationResponse RegisterOutput(OutputRegistrationRequest request)
{
- if (!TryGetRound(request.RoundId, out var round))
+ lock (Lock)
+ {
+ if (!Rounds.TryGetValue(request.RoundId, out var round))
{
throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.RoundNotFound);
}
@@ -167,16 +218,70 @@ namespace WalletWasabi.WabiSabi.Backend.Rounds
throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.IncorrectRequestedWeightCredentials);
}
- return round.RegisterBob(bob, request.AmountCredentialRequests, weightCredentialRequests);
+ if (round.Phase != Phase.OutputRegistration)
+ {
+ throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.WrongPhase);
+ }
+
+ var amountCredentialResponse = round.AmountCredentialIssuer.HandleRequest(request.AmountCredentialRequests);
+ var weightCredentialResponse = round.WeightCredentialIssuer.HandleRequest(weightCredentialRequests);
+
+ round.Bobs.Add(bob);
+
+ return new(
+ round.UnsignedTxSecret,
+ amountCredentialResponse,
+ weightCredentialResponse);
+ }
}
public void SignTransaction(TransactionSignaturesRequest request)
{
- if (!TryGetRound(request.RoundId, out var round))
+ lock (Lock)
+ {
+ if (!Rounds.TryGetValue(request.RoundId, out var round))
{
throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.RoundNotFound);
}
- round.SubmitTransactionSignatures(request.InputWitnessPairs);
+
+ if (round.Phase != Phase.TransactionSigning)
+ {
+ throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.WrongPhase);
+ }
+ foreach (var inputWitnessPair in request.InputWitnessPairs)
+ {
+ var index = (int)inputWitnessPair.InputIndex;
+ var witness = inputWitnessPair.Witness;
+
+ // If input is already signed, don't bother.
+ if (round.Coinjoin.Inputs[index].HasWitScript())
+ {
+ continue;
+ }
+
+ // Verify witness.
+ // 1. Copy UnsignedCoinJoin.
+ Transaction cjCopy = Transaction.Parse(round.Coinjoin.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 = round.Alices.SelectMany(x => x.Coins).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))
+ {
+ throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.WrongCoinjoinSignature);
+ }
+
+ // Finally add it to our CJ.
+ round.Coinjoin.Inputs[index].WitScript = witness;
+ }
+ }
}
}
}

MODIFY
WalletWasabi/WabiSabi/Backend/Rounds/Round.cs
WalletWasabi/WabiSabi/Backend/Rounds/Round.cs
@@ -65,49 +65,11 @@ namespace WalletWasabi.WabiSabi.Backend.Rounds
public Round? BlameOf { get; } = null;
public bool IsBlameRound => BlameOf is not null;
public ISet<OutPoint> BlameWhitelist { get; } = new HashSet<OutPoint>();
- private object Lock { get; } = new();
public byte[] UnsignedTxSecret { get; }
public Transaction Coinjoin { get; }
public RoundParameters RoundParameters { get; }
- public InputsRegistrationResponse RegisterAlice(
- Alice alice,
- ZeroCredentialsRequest zeroAmountCredentialRequests,
- ZeroCredentialsRequest zeroWeightCredentialRequests)
- {
- lock (Lock)
- {
- if (Phase != Phase.InputRegistration)
- {
- throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.WrongPhase);
- }
-
- var amountCredentialResponse = AmountCredentialIssuer.HandleRequest(zeroAmountCredentialRequests);
- var weightCredentialResponse = WeightCredentialIssuer.HandleRequest(zeroWeightCredentialRequests);
-
- alice.SetDeadlineRelativeTo(ConnectionConfirmationTimeout);
- foreach (var op in alice.Coins.Select(x => x.Outpoint))
- {
- if (RemoveAlicesNoLock(x => x.Coins.Select(x => x.Outpoint).Contains(op)) > 0)
- {
- Logger.LogInfo("Updated Alice registration.");
- }
- }
- Alices.Add(alice);
-
- return new(alice.Id, amountCredentialResponse, weightCredentialResponse);
- }
- }
-
public int RemoveAlices(Predicate<Alice> match)
- {
- lock (Lock)
- {
- return RemoveAlicesNoLock(match);
- }
- }
-
- private int RemoveAlicesNoLock(Predicate<Alice> match)
{
if (Phase != Phase.InputRegistration)
{
@@ -115,120 +77,5 @@ namespace WalletWasabi.WabiSabi.Backend.Rounds
}
return Alices.RemoveAll(match);
}
-
- public ConnectionConfirmationResponse ConfirmAlice(ConnectionConfirmationRequest request)
- {
- lock (Lock)
- {
- var alice = Alices.FirstOrDefault(x => x.Id == request.AliceId);
- if (alice is null)
- {
- throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.AliceNotFound);
- }
-
- var amountZeroCredentialResponse = AmountCredentialIssuer.HandleRequest(request.ZeroAmountCredentialRequests);
- var weightZeroCredentialResponse = WeightCredentialIssuer.HandleRequest(request.ZeroWeightCredentialRequests);
-
- var realAmountCredentialRequests = request.RealAmountCredentialRequests;
- var realWeightCredentialRequests = request.RealWeightCredentialRequests;
-
- if (realWeightCredentialRequests.Delta != alice.CalculateRemainingWeightCredentials(RegistrableWeightCredentials))
- {
- throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.IncorrectRequestedWeightCredentials);
- }
- if (realAmountCredentialRequests.Delta != alice.CalculateRemainingAmountCredentials(FeeRate))
- {
- throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.IncorrectRequestedAmountCredentials);
- }
-
- if (Phase == Phase.InputRegistration)
- {
- alice.SetDeadlineRelativeTo(ConnectionConfirmationTimeout);
- return new(
- amountZeroCredentialResponse,
- weightZeroCredentialResponse);
- }
- else if (Phase == Phase.ConnectionConfirmation)
- {
- var amountRealCredentialResponse = AmountCredentialIssuer.HandleRequest(realAmountCredentialRequests);
- var weightRealCredentialResponse = WeightCredentialIssuer.HandleRequest(realWeightCredentialRequests);
-
- return new(
- amountZeroCredentialResponse,
- weightZeroCredentialResponse,
- amountRealCredentialResponse,
- weightRealCredentialResponse);
- }
- else
- {
- throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.WrongPhase);
- }
- }
- }
-
- public OutputRegistrationResponse RegisterBob(Bob bob, RealCredentialsRequest amountCredentialRequests, RealCredentialsRequest weightCredentialRequests)
- {
- lock (Lock)
- {
- if (Phase != Phase.OutputRegistration)
- {
- throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.WrongPhase);
- }
-
- var amountCredentialResponse = AmountCredentialIssuer.HandleRequest(amountCredentialRequests);
- var weightCredentialResponse = WeightCredentialIssuer.HandleRequest(weightCredentialRequests);
-
- Bobs.Add(bob);
-
- return new(
- UnsignedTxSecret,
- amountCredentialResponse,
- weightCredentialResponse);
- }
- }
-
- public void SubmitTransactionSignatures(IEnumerable<InputWitnessPair> inputWitnessPairs)
- {
- lock (Lock)
- {
- if (Phase != Phase.TransactionSigning)
- {
- throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.WrongPhase);
- }
- foreach (var inputWitnessPair in inputWitnessPairs)
- {
- var index = (int)inputWitnessPair.InputIndex;
- var witness = inputWitnessPair.Witness;
-
- // If input is already signed, don't bother.
- if (Coinjoin.Inputs[index].HasWitScript())
- {
- continue;
- }
-
- // Verify witness.
- // 1. Copy UnsignedCoinJoin.
- Transaction cjCopy = Transaction.Parse(Coinjoin.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 = Alices.SelectMany(x => x.Coins).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))
- {
- throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.WrongCoinjoinSignature);
- }
-
- // Finally add it to our CJ.
- Coinjoin.Inputs[index].WitScript = witness;
- }
- }
- }
}
}

Make the round a pure container class