Add Reissuance Request
MODIFY
WalletWasabi/WabiSabi/Backend/Models/WabiSabiProtocolErrorCode.cs
WalletWasabi/WabiSabi/Backend/Models/WabiSabiProtocolErrorCode.cs
@@ -34,6 +34,7 @@ namespace WalletWasabi.WabiSabi.Backend.Models
SizeLimitExceeded,
DustOutput,
UneconomicalInput,
- TooMuchTotalWeight
+ TooMuchTotalWeight,
+ DeltaNotZero
}
}

MODIFY
WalletWasabi/WabiSabi/Backend/PostRequests/ArenaRequestHandler.cs
WalletWasabi/WabiSabi/Backend/PostRequests/ArenaRequestHandler.cs
@@ -96,6 +96,15 @@ namespace WalletWasabi.WabiSabi.Backend.PostRequests
}
}
+ public async Task<ReissuanceResponse> ReissuanceAsync(ReissuanceRequest request)
+ {
+ DisposeGuard();
+ using (RunningTasks.RememberWith(RunningRequests))
+ {
+ return await Arena.ReissuanceAsync(request).ConfigureAwait(false);
+ }
+ }
+
public async ValueTask DisposeAsync()
{
lock (DisposeStartedLock)

MODIFY
WalletWasabi/WabiSabi/Backend/PostRequests/IArenaRequestHandler.cs
WalletWasabi/WabiSabi/Backend/PostRequests/IArenaRequestHandler.cs
@@ -6,9 +6,15 @@ namespace WalletWasabi.WabiSabi.Backend.PostRequests
public interface IArenaRequestHandler
{
Task<InputsRegistrationResponse> RegisterInputAsync(InputsRegistrationRequest request);
+
Task<ConnectionConfirmationResponse> ConfirmConnectionAsync(ConnectionConfirmationRequest request);
+
Task<OutputRegistrationResponse> RegisterOutputAsync(OutputRegistrationRequest request);
+
Task RemoveInputAsync(InputsRemovalRequest request);
+
Task SignTransactionAsync(TransactionSignaturesRequest request);
+
+ Task<ReissuanceResponse> ReissuanceAsync(ReissuanceRequest request);
}
}

MODIFY
WalletWasabi/WabiSabi/Backend/Rounds/Arena.cs
WalletWasabi/WabiSabi/Backend/Rounds/Arena.cs
@@ -457,6 +457,45 @@ namespace WalletWasabi.WabiSabi.Backend.Rounds
}
}
+ public async Task<ReissuanceResponse> ReissuanceAsync(ReissuanceRequest request)
+ {
+ using (await AsyncLock.LockAsync().ConfigureAwait(false))
+ {
+ if (!Rounds.TryGetValue(request.RoundId, out var round))
+ {
+ throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.RoundNotFound, $"Round ({request.RoundId}) not found.");
+ }
+
+ if (request.RealAmountCredentialRequests.Delta != 0)
+ {
+ throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.DeltaNotZero, $"Round ({request.RoundId}): Delta can be zero only at reissuance.");
+ }
+
+ if (request.RealAmountCredentialRequests.Requested.Count() != 2)
+ {
+ throw new NotImplementedException(); // TODO
+ }
+
+ if (request.ZeroAmountCredentialRequests.Requested.Count() != 2)
+ {
+ throw new NotImplementedException(); // TODO
+ }
+
+ if (round.Phase != Phase.ConnectionConfirmation || round.Phase != Phase.OutputRegistration)
+ {
+ throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.WrongPhase, $"Round ({request.RoundId}): Wrong phase ({round.Phase}).");
+ }
+
+ var commitRealAmountCredentialResponse = round.AmountCredentialIssuer.PrepareResponse(request.RealAmountCredentialRequests);
+ var commitZeroAmountCredentialResponse = round.AmountCredentialIssuer.PrepareResponse(request.ZeroAmountCredentialRequests);
+
+ return new(
+ commitRealAmountCredentialResponse.Commit(),
+ commitZeroAmountCredentialResponse.Commit()
+ );
+ }
+ }
+
public override void Dispose()
{
Random.Dispose();

MODIFY
WalletWasabi/WabiSabi/Client/ArenaClient.cs
WalletWasabi/WabiSabi/Client/ArenaClient.cs
@@ -114,6 +114,31 @@ namespace WalletWasabi.WabiSabi.Client
WeightCredentialClient.HandleResponse(outputRegistrationResponse.WeightCredentials, realWeightCredentialResponseValidation);
}
+ public async Task ReissuanceAsync(Guid roundId, long value, IEnumerable<Credential> amountCredentialsToPresent)
+ {
+ Guard.InRange(nameof(amountCredentialsToPresent), amountCredentialsToPresent, 0, AmountCredentialClient.NumberOfCredentials);
+
+ var presentedAmount = amountCredentialsToPresent.Sum(x => (long)x.Amount.ToUlong());
+
+ var amount1 = value;
+ var amount2 = presentedAmount - value;
+
+ var (realAmountCredentialRequest, realAmountCredentialResponseValidation) = AmountCredentialClient.CreateRequest(
+ new[] { amount1, amount2 },
+ amountCredentialsToPresent);
+
+ var zeroAmountCredentialRequestData = AmountCredentialClient.CreateRequestForZeroAmount();
+
+ var reissuanceResponse = await RequestHandler.ReissuanceAsync(
+ new ReissuanceRequest(
+ roundId,
+ realAmountCredentialRequest,
+ zeroAmountCredentialRequestData.CredentialsRequest)).ConfigureAwait(false);
+
+ AmountCredentialClient.HandleResponse(reissuanceResponse.RealAmountCredentials, realAmountCredentialResponseValidation);
+ AmountCredentialClient.HandleResponse(reissuanceResponse.ZeroAmountCredentials, zeroAmountCredentialRequestData.CredentialsResponseValidation);
+ }
+
public async Task<bool> ConfirmConnectionAsync(Guid roundId, Guid aliceId, IEnumerable<long> inputsRegistrationWeight, IEnumerable<Credential> amountCredentialsToPresent, IEnumerable<Money> newAmount)
{
Guard.InRange(nameof(newAmount), newAmount, 1, AmountCredentialClient.NumberOfCredentials);

ADD
WalletWasabi/WabiSabi/Models/ReissuanceRequest.cs
+using System;
+using WalletWasabi.WabiSabi.Crypto.CredentialRequesting;
+
+namespace WalletWasabi.WabiSabi.Models
+{
+ public record ReissuanceRequest(
+ Guid RoundId,
+ RealCredentialsRequest RealAmountCredentialRequests,
+ ZeroCredentialsRequest ZeroAmountCredentialRequests
+ );
+}

ADD
WalletWasabi/WabiSabi/Models/ReissuanceResponse.cs
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using WalletWasabi.WabiSabi.Crypto.CredentialRequesting;
+
+namespace WalletWasabi.WabiSabi.Models
+{
+ public record ReissuanceResponse(
+ CredentialsResponse RealAmountCredentials,
+ CredentialsResponse ZeroAmountCredentials
+ );
+}

Add Reissuance Request