Add ReadyToSign
MODIFY
WalletWasabi.Backend/Controllers/WabiSabiController.cs
WalletWasabi.Backend/Controllers/WabiSabiController.cs
@@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Mvc;
using WalletWasabi.Backend.Filters;
using WalletWasabi.WabiSabi.Backend.PostRequests;
using WalletWasabi.WabiSabi.Models;
+
using WalletWasabi.Backend.Filters;
namespace WalletWasabi.Backend.Controllers
@@ -67,5 +68,11 @@ namespace WalletWasabi.Backend.Controllers
{
return Task.FromResult(RequestHandler.Arena.Rounds.Select(x => RoundState.FromRound(x)).ToArray());
}
+
+ [HttpPost("ready-to-sign")]
+ public Task ReadyToSign(ReadyToSignRequestRequest request, CancellationToken cancellableToken)
+ {
+ return RequestHandler.ReadyToSignAsync(request, cancellableToken);
+ }
}
}

MODIFY
WalletWasabi.Tests/Helpers/ArenaRequestHandlerAdapter.cs
WalletWasabi.Tests/Helpers/ArenaRequestHandlerAdapter.cs
@@ -36,5 +36,8 @@ namespace WalletWasabi.Tests.Helpers
public Task SignTransactionAsync(TransactionSignaturesRequest request, CancellationToken cancellationToken)
=> arena.SignTransactionAsync(request);
+
+ public Task ReadyToSign(ReadyToSignRequestRequest request, CancellationToken cancellationToken)
+ => arena.ReadyToSignAsync(request);
}
}

MODIFY
WalletWasabi/WabiSabi/Backend/Models/Alice.cs
WalletWasabi/WabiSabi/Backend/Models/Alice.cs
@@ -23,6 +23,7 @@ namespace WalletWasabi.WabiSabi.Backend.Models
public int TotalInputVsize => Coin.ScriptPubKey.EstimateInputVsize();
public bool ConfirmedConnection { get; set; } = false;
+ public bool ReadyToSign { get; set; }
public long CalculateRemainingVsizeCredentials(int maxRegistrableSize) => maxRegistrableSize - TotalInputVsize;

MODIFY
WalletWasabi/WabiSabi/Backend/PostRequests/ArenaRequestHandler.cs
WalletWasabi/WabiSabi/Backend/PostRequests/ArenaRequestHandler.cs
@@ -84,6 +84,15 @@ namespace WalletWasabi.WabiSabi.Backend.PostRequests
}
}
+ public async Task ReadyToSignAsync(ReadyToSignRequestRequest request, CancellationToken cancellableToken)
+ {
+ DisposeGuard();
+ using (RunningTasks.RememberWith(RunningRequests))
+ {
+ await Arena.ReadyToSignAsync(request).ConfigureAwait(false);
+ }
+ }
+
public async ValueTask DisposeAsync()
{
lock (DisposeStartedLock)

MODIFY
WalletWasabi/WabiSabi/Backend/PostRequests/IWabiSabiApiRequestHandler.cs
WalletWasabi/WabiSabi/Backend/PostRequests/IWabiSabiApiRequestHandler.cs
@@ -19,5 +19,7 @@ namespace WalletWasabi.WabiSabi.Backend.PostRequests
Task<ReissueCredentialResponse> ReissueCredentialAsync(ReissueCredentialRequest request, CancellationToken cancellationToken);
Task<RoundState[]> GetStatusAsync(CancellationToken cancellationToken);
+
+ Task ReadyToSign(ReadyToSignRequestRequest request, CancellationToken cancellationToken);
}
}

MODIFY
WalletWasabi/WabiSabi/Backend/Rounds/Arena.cs
WalletWasabi/WabiSabi/Backend/Rounds/Arena.cs
@@ -8,6 +8,7 @@ using System.Threading;
using System.Threading.Tasks;
using WalletWasabi.Bases;
using WalletWasabi.BitcoinCore.Rpc;
+using WalletWasabi.Crypto;
using WalletWasabi.Crypto.Randomness;
using WalletWasabi.WabiSabi.Backend.Banning;
using WalletWasabi.WabiSabi.Backend.Models;
@@ -115,7 +116,9 @@ namespace WalletWasabi.WabiSabi.Backend.Rounds
long aliceSum = round.Alices.Sum(x => x.CalculateRemainingAmountCredentials(round.FeeRate));
long bobSum = round.Bobs.Sum(x => x.CredentialAmount);
var diff = aliceSum - bobSum;
- if (diff == 0 || round.OutputRegistrationStart + round.OutputRegistrationTimeout < DateTimeOffset.UtcNow)
+ var allReady = round.Alices.All(a => a.ReadyToSign);
+
+ if (allReady || round.OutputRegistrationStart + round.OutputRegistrationTimeout < DateTimeOffset.UtcNow)
{
var coinjoin = round.Assert<ConstructionState>();
@@ -266,6 +269,22 @@ namespace WalletWasabi.WabiSabi.Backend.Rounds
}
}
+ public async Task ReadyToSignAsync(ReadyToSignRequestRequest request)
+ {
+ using (await AsyncLock.LockAsync().ConfigureAwait(false))
+ {
+ var alice = Rounds.Single(r => r.Id == request.RoundId).Alices.Single(a => a.Id == request.AliceId);
+
+ var coinJoinInputCommitmentData = new CoinJoinInputCommitmentData("CoinJoinCoordinatorIdentifier", request.RoundId);
+ if (!OwnershipProof.VerifyCoinJoinInputProof(request.OwnershipProof, alice.Coin.TxOut.ScriptPubKey, coinJoinInputCommitmentData))
+ {
+ throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.WrongOwnershipProof);
+ }
+
+ alice.ReadyToSign = true;
+ }
+ }
+
public async Task RemoveInputAsync(InputsRemovalRequest request)
{
using (await AsyncLock.LockAsync().ConfigureAwait(false))

MODIFY
WalletWasabi/WabiSabi/Client/AliceClient.cs
WalletWasabi/WabiSabi/Client/AliceClient.cs
@@ -114,8 +114,10 @@ namespace WalletWasabi.WabiSabi.Client
Logger.LogInfo($"Round ({RoundId}), Alice ({AliceId}): Posted a signature.");
}
- public async Task SignalReadyAsync()
+ public async Task ReadyToSignAsync(CancellationToken cancellationToken)
{
+ await ArenaClient.ReadyToSignAsync(RoundId, AliceId, BitcoinSecret.PrivateKey, cancellationToken).ConfigureAwait(false);
+ Logger.LogInfo($"Round ({RoundId}), Alice ({AliceId}): Inputs removed.");
}
private static uint256 CalculateHash(Coin coin, BitcoinSecret bitcoinSecret, uint256 roundId)

MODIFY
WalletWasabi/WabiSabi/Client/ArenaClient.cs
WalletWasabi/WabiSabi/Client/ArenaClient.cs
@@ -230,5 +230,23 @@ namespace WalletWasabi.WabiSabi.Client
{
return await RequestHandler.GetStatusAsync(cancellationToken).ConfigureAwait(false);
}
+
+ public async Task ReadyToSignAsync(
+ uint256 roundId,
+ uint256 aliceId,
+ Key key,
+ CancellationToken cancellationToken)
+ {
+ var ownershipProof = OwnershipProof.GenerateCoinJoinInputProof(
+ key,
+ new CoinJoinInputCommitmentData("CoinJoinCoordinatorIdentifier", roundId));
+
+ await RequestHandler.ReadyToSign(
+ new ReadyToSignRequestRequest(
+ roundId,
+ aliceId,
+ ownershipProof),
+ cancellationToken).ConfigureAwait(false);
+ }
}
}

MODIFY
WalletWasabi/WabiSabi/Client/CoinJoinClient.cs
WalletWasabi/WabiSabi/Client/CoinJoinClient.cs
@@ -79,6 +79,9 @@ namespace WalletWasabi.WabiSabi.Client
roundState = await RoundStatusUpdater.CreateRoundAwaiter(roundState.Id, rs => rs.Phase == Phase.OutputRegistration, cancellationToken).ConfigureAwait(false);
await dgr.StartOutputRegistrationsAsync(outputTxOuts, bobClient, cancellationToken).ConfigureAwait(false);
+ // ReadyToSign.
+ await ReadyToSignAsync(aliceClients, cancellationToken).ConfigureAwait(false);
+
// Signing.
roundState = await RoundStatusUpdater.CreateRoundAwaiter(roundState.Id, rs => rs.Phase == Phase.TransactionSigning, cancellationToken).ConfigureAwait(false);
var signingState = roundState.Assert<SigningState>();
@@ -166,5 +169,16 @@ namespace WalletWasabi.WabiSabi.Client
var signingRequests = aliceClients.Select(SignTransactionTask);
await Task.WhenAll(signingRequests).ConfigureAwait(false);
}
+
+ private async Task ReadyToSignAsync(IEnumerable<AliceClient> aliceClients, CancellationToken cancellationToken)
+ {
+ async Task ReadyToSignTask(AliceClient aliceClient)
+ {
+ await aliceClient.ReadyToSignAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+ var readyRequests = aliceClients.Select(ReadyToSignTask);
+ await Task.WhenAll(readyRequests).ConfigureAwait(false);
+ }
}
}

MODIFY
WalletWasabi/WabiSabi/Client/WabiSabiHttpApiClient.cs
WalletWasabi/WabiSabi/Client/WabiSabiHttpApiClient.cs
@@ -30,7 +30,8 @@ namespace WalletWasabi.WabiSabi.Client
RegisterOutput,
ReissueCredential,
SignTransaction,
- GetStatus
+ GetStatus,
+ ReadyToSign
}
public Task<InputRegistrationResponse> RegisterInputAsync(InputRegistrationRequest request, CancellationToken cancellationToken) =>
@@ -63,6 +64,9 @@ namespace WalletWasabi.WabiSabi.Client
return Deserialize<RoundState[]>(jsonString);
}
+ public Task ReadyToSign(ReadyToSignRequestRequest request, CancellationToken cancellationToken) =>
+ SendAndReceiveAsync<ReadyToSignRequestRequest>(RemoteAction.ReadyToSign, request, cancellationToken);
+
private async Task<string> SendAsync<TRequest>(RemoteAction action, TRequest request, CancellationToken cancellationToken) where TRequest : class
{
using var content = Serialize(request);
@@ -95,7 +99,7 @@ namespace WalletWasabi.WabiSabi.Client
private static TResponse Deserialize<TResponse>(string jsonString)
{
return JsonConvert.DeserializeObject<TResponse>(jsonString, JsonSerializationOptions.Default.Settings)
- ?? throw new InvalidOperationException("Deserealization error");
+ ?? throw new InvalidOperationException("Deserialization error");
}
private static string GetUriEndPoint(RemoteAction action) =>
@@ -108,6 +112,7 @@ namespace WalletWasabi.WabiSabi.Client
RemoteAction.RemoveInput => "input-unregistration",
RemoteAction.SignTransaction => "transaction-signature",
RemoteAction.GetStatus => "status",
+ RemoteAction.ReadyToSign => "ready-to-sign",
_ => throw new NotSupportedException($"Action '{action}' is unknown and has no endpoint associated.")
};
}

ADD
WalletWasabi/WabiSabi/Models/ReadyToSignRequestRequest.cs
+using NBitcoin;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using WalletWasabi.Crypto;
+
+namespace WalletWasabi.WabiSabi.Backend.PostRequests
+{
+ public record ReadyToSignRequestRequest(
+ uint256 RoundId,
+ uint256 AliceId,
+ OwnershipProof OwnershipProof);
+}

Add ReadyToSign