Give back signatures at connection confirmation (tests fail)
MODIFY
WalletWasabi.Backend/Controllers/ChaumianCoinJoinController.cs
WalletWasabi.Backend/Controllers/ChaumianCoinJoinController.cs
@@ -310,6 +310,7 @@ namespace WalletWasabi.Backend.Controllers
BigInteger blindSignature = signer.Sign(blindedOutput);
blindSignatures.Add(blindSignature);
}
+ alice.BlindedOutputSignatures = blindSignatures;
// Check if phase changed since.
if (round.Status != CcjRoundStatus.Running || round.Phase != CcjRoundPhase.InputRegistration)
@@ -331,7 +332,6 @@ namespace WalletWasabi.Backend.Controllers
var resp = new InputsResponse
{
UniqueId = alice.UniqueId,
- BlindedOutputSignatures = blindSignatures,
RoundId = round.RoundId
};
return Ok(resp);
@@ -349,8 +349,8 @@ namespace WalletWasabi.Backend.Controllers
/// </summary>
/// <param name="uniqueId">Unique identifier, obtained previously.</param>
/// <param name="roundId">Round identifier, obtained previously.</param>
- /// <returns>Current Phase and Alice is found.</returns>
- /// <response code="200">Current Phase and Alice is found.</response>
+ /// <returns>Current phase and blinded output sinatures if Alice is found.</returns>
+ /// <response code="200">Current phase and blinded output sinatures if Alice is found.</response>
/// <response code="400">The provided uniqueId or roundId was malformed.</response>
/// <response code="404">If Alice or the round is not found.</response>
/// <response code="410">Participation can be only confirmed from a Running round's InputRegistration or ConnectionConfirmation phase.</response>
@@ -373,6 +373,13 @@ namespace WalletWasabi.Backend.Controllers
}
CcjRoundPhase phase = round.Phase;
+
+ // Start building the response.
+ var resp = new ConnConfResp
+ {
+ CurrentPhase = phase
+ };
+
switch (phase)
{
case CcjRoundPhase.InputRegistration:
@@ -384,6 +391,8 @@ namespace WalletWasabi.Backend.Controllers
{
alice.State = AliceState.ConnectionConfirmed;
+ resp.BlindedOutputSignatures = alice.BlindedOutputSignatures.Take(round.CountUsedMixingLevels()); // Don't give back more mixing levels than we'll use.
+
// Progress round if needed.
if (round.AllAlices(AliceState.ConnectionConfirmed))
{
@@ -398,7 +407,7 @@ namespace WalletWasabi.Backend.Controllers
}
}
- return Ok(phase);
+ return Ok(resp);
}
/// <summary>

MODIFY
WalletWasabi.Tests/ModelTests.cs
WalletWasabi.Tests/ModelTests.cs
@@ -209,17 +209,12 @@ namespace WalletWasabi.Tests
{
UniqueId = Guid.NewGuid(),
RoundId = 1,
- BlindedOutputSignatures = bigIntegers
};
var serialized = JsonConvert.SerializeObject(resp);
var deserialized = JsonConvert.DeserializeObject<InputsResponse>(serialized);
Assert.Equal(resp.RoundId, deserialized.RoundId);
Assert.Equal(resp.UniqueId, deserialized.UniqueId);
- for (int i = 0; i < bigIntegers.Length; i++)
- {
- Assert.Equal(bigIntegers[i], deserialized.BlindedOutputSignatures.ToArray()[i]);
- }
}
[Fact]

MODIFY
WalletWasabi.Tests/RegTests.cs
WalletWasabi.Tests/RegTests.cs
@@ -1836,14 +1836,12 @@ namespace WalletWasabi.Tests
ClientRoundRegistration first = null;
var second = new ClientRoundRegistration(aliceClient,
new[] { new SmartCoin(uint256.One, 1, Script.Empty, Money.Zero, new[] { new TxoRef(uint256.One, 1) }, Height.Unknown, true, 1) },
- new List<(BitcoinAddress, BlindSignature, int)> { (BitcoinAddress.Create("12Rty3c8j3QiZSwLVaBtch6XUMZaja3RC7", Network.Main), null, 1) },
BitcoinAddress.Create("12Rty3c8j3QiZSwLVaBtch6XUMZaja3RC7", Network.Main));
first = second;
second = null;
Assert.NotNull(first);
Assert.Null(second);
- Assert.NotNull(aliceClient.BlindedOutputSignatures);
Assert.NotEqual(Guid.Empty, aliceClient.UniqueId);
Assert.True(aliceClient.RoundId > 0);
@@ -1872,7 +1870,6 @@ namespace WalletWasabi.Tests
inputsRequest.Inputs.First().Proof = proof;
using (var aliceClient = await AliceClient.CreateNewAsync(network, inputsRequest, baseUri))
{
- Assert.NotNull(aliceClient.BlindedOutputSignatures);
Assert.NotEqual(Guid.Empty, aliceClient.UniqueId);
Assert.True(aliceClient.RoundId > 0);
@@ -1910,7 +1907,6 @@ namespace WalletWasabi.Tests
inputsRequest.Inputs = inputProofs;
using (var aliceClient = await AliceClient.CreateNewAsync(network, inputsRequest, baseUri))
{
- Assert.NotEmpty(aliceClient.BlindedOutputSignatures);
Assert.NotEqual(Guid.Empty, aliceClient.UniqueId);
Assert.True(aliceClient.RoundId > 0);
@@ -1980,7 +1976,6 @@ namespace WalletWasabi.Tests
using (var aliceClient = await AliceClient.CreateNewAsync(network, new Key().PubKey.GetAddress(network), new[] { blindedData }, new InputProofModel[] { new InputProofModel { Input = coin.Outpoint.ToTxoRef(), Proof = key.SignCompact(blindedOutputScriptsHash) } }, baseUri))
{
- Assert.NotEmpty(aliceClient.BlindedOutputSignatures);
Assert.NotEqual(Guid.Empty, aliceClient.UniqueId);
Assert.True(aliceClient.RoundId > 0);
// Double the request.
@@ -2020,7 +2015,6 @@ namespace WalletWasabi.Tests
using (var aliceClient = await AliceClient.CreateNewAsync(network, new Key().PubKey.GetAddress(network), new[] { blindedData }, new InputProofModel[] { new InputProofModel { Input = coin.Outpoint.ToTxoRef(), Proof = key.SignCompact(blindedOutputScriptsHash) } }, baseUri))
{
- Assert.NotEmpty(aliceClient.BlindedOutputSignatures);
Assert.NotEqual(Guid.Empty, aliceClient.UniqueId);
Assert.True(aliceClient.RoundId > 0);
await aliceClient.PostUnConfirmationAsync();
@@ -2086,28 +2080,29 @@ namespace WalletWasabi.Tests
Assert.Equal(aliceClient2.RoundId, aliceClient1.RoundId);
Assert.NotEqual(aliceClient2.UniqueId, aliceClient1.UniqueId);
- BlindSignature unblindedSignature1 = requester1.UnblindSignature(aliceClient1.BlindedOutputSignatures.First());
- BlindSignature unblindedSignature2 = requester2.UnblindSignature(aliceClient2.BlindedOutputSignatures.First());
+ aliceClient1.Requesters = new[] { requester1 };
+ aliceClient2.Requesters = new[] { requester2 };
- if (!round.MixingLevels.GetBaseLevel().Signer.VerifyUnblindedSignature(unblindedSignature2, outputAddress2.ScriptPubKey.ToBytes()))
- {
- throw new NotSupportedException("Coordinator did not sign the blinded output properly.");
- }
-
- var phase = await aliceClient1.PostConfirmationAsync();
- Assert.Equal(phase, await aliceClient1.PostConfirmationAsync()); // Make sure it won't throw error for double confirming.
- Assert.Equal(phase, await aliceClient2.PostConfirmationAsync());
+ var connConfResp = await aliceClient1.PostConfirmationAsync();
+ var connConfResp2 = await aliceClient2.PostConfirmationAsync();
+ Assert.Equal(connConfResp.currentPhase, (await aliceClient1.PostConfirmationAsync()).currentPhase); // Make sure it won't throw error for double confirming.
+ Assert.Equal(connConfResp.currentPhase, connConfResp2.currentPhase);
httpRequestException = await Assert.ThrowsAsync<HttpRequestException>(async () => await aliceClient2.PostConfirmationAsync());
Assert.Equal($"{HttpStatusCode.Gone.ToReasonString()}\nParticipation can be only confirmed from InputRegistration or ConnectionConfirmation phase. Current phase: OutputRegistration.", httpRequestException.Message);
var roundState = await satoshiClient.GetRoundStateAsync(aliceClient1.RoundId);
Assert.Equal(CcjRoundPhase.OutputRegistration, roundState.Phase);
+ if (!round.MixingLevels.GetBaseLevel().Signer.VerifyUnblindedSignature(connConfResp2.activeOutputs.First().signature, outputAddress2.ScriptPubKey.ToBytes()))
+ {
+ throw new NotSupportedException("Coordinator did not sign the blinded output properly.");
+ }
+
using (var bobClient1 = new BobClient(baseUri))
using (var bobClient2 = new BobClient(baseUri))
{
- await bobClient1.PostOutputAsync(aliceClient1.RoundId, outputAddress1, unblindedSignature1, 0);
- await bobClient2.PostOutputAsync(aliceClient2.RoundId, outputAddress2, unblindedSignature2, 0);
+ await bobClient1.PostOutputAsync(aliceClient1.RoundId, outputAddress1, connConfResp.activeOutputs.First().signature, 0);
+ await bobClient2.PostOutputAsync(aliceClient2.RoundId, outputAddress2, connConfResp2.activeOutputs.First().signature, 0);
}
roundState = await satoshiClient.GetRoundStateAsync(aliceClient1.RoundId);
@@ -2210,7 +2205,6 @@ namespace WalletWasabi.Tests
// important data for each of the participants in the coinjoin session.
var participants = new List<(AliceClient aliceClient,
List<(Requester requester, BitcoinWitPubKeyAddress outputAddress, uint256 blindedScript)> outouts,
- List<BlindSignature> signatures,
List<(TxoRef input, byte[] proof, Coin coin, Key key)> inputs)>();
// INPUS REGISTRATION PHASE --
@@ -2258,47 +2252,35 @@ namespace WalletWasabi.Tests
var inputProof = inputs.Select(x => new InputProofModel { Input = x.input, Proof = x.proof });
var aliceClient = await AliceClient.CreateNewAsync(network, changeOutput, blindedOutputScriptList, inputProof, baseUri);
- // Coordinator only signs as outputs that can be funded by the registered inputs
- Assert.True(aliceClient.BlindedOutputSignatures.Length < blindedOutputScriptList.Count());
-
// We check the coordinator signed all the alice blinded outputs
- var i = 0;
- var signatures = new List<BlindSignature>();
- foreach (var signature in aliceClient.BlindedOutputSignatures)
- {
- var (requester, outputsAddress, _) = outputs[i];
- BlindSignature unblindedSignature = requester.UnblindSignature(signature);
+ aliceClient.Requesters = outputs.Select(x => x.requester).ToArray();
+ aliceClient.RegisteredAddresses = outputs.Select(x => x.outputAddress).ToArray();
- var level = round.MixingLevels.Levels[i];
- if (!level.Signer.VerifyUnblindedSignature(unblindedSignature, outputsAddress.ScriptPubKey.ToBytes()))
- {
- throw new NotSupportedException("Coordinator did not sign the blinded output properly.");
- }
- signatures.Add(unblindedSignature);
- i++;
- }
-
- participants.Add((aliceClient, outputs, signatures, inputs));
+ participants.Add((aliceClient, outputs, inputs));
}
// CONNECTION CONFIRMATION PHASE --
- foreach (var (aliceClient, _, _, _) in participants)
+ List<IEnumerable<(BitcoinAddress output, BlindSignature signature, int level)>> activeOutputs = new List<IEnumerable<(BitcoinAddress output, BlindSignature signature, int level)>>();
+ var j = 0;
+ foreach (var (aliceClient, _, _) in participants)
{
- var phase = await aliceClient.PostConfirmationAsync();
+ var res = await aliceClient.PostConfirmationAsync();
+ activeOutputs.Add(res.activeOutputs);
+ j++;
}
// OUTPUTS REGISTRATION PHASE --
var roundState = await satoshiClient.GetRoundStateAsync(roundId);
Assert.Equal(CcjRoundPhase.OutputRegistration, roundState.Phase);
- foreach (var (aliceClient, outputs, signatures, _) in participants)
+ foreach (var (aliceClient, outputs, _) in participants)
{
using (var bobClient = new BobClient(baseUri))
{
var i = 0;
- foreach (var output in outputs.Take(aliceClient.BlindedOutputSignatures.Length))
+ foreach (var output in outputs.Take(activeOutputs[i].Count()))
{
- await bobClient.PostOutputAsync(aliceClient.RoundId, output.outputAddress, signatures[i], i);
+ await bobClient.PostOutputAsync(aliceClient.RoundId, output.outputAddress, activeOutputs[i].ElementAt(i).signature, i);
i++;
}
}
@@ -2309,7 +2291,7 @@ namespace WalletWasabi.Tests
Assert.Equal(CcjRoundPhase.Signing, roundState.Phase);
uint256 transactionId = null;
- foreach (var (aliceClient, outputs, _, inputs) in participants)
+ foreach (var (aliceClient, outputs, inputs) in participants)
{
var unsignedTransaction = await aliceClient.GetUnsignedCoinJoinAsync();
transactionId = unsignedTransaction.GetHash();
@@ -2515,12 +2497,12 @@ namespace WalletWasabi.Tests
Assert.Equal(roundId, aliceClient.RoundId);
}
// Because it's valuetuple.
- users.Add((user.requester, user.blinded, user.activeOutputAddress, user.changeOutputAddress, user.inputProofModels, user.userInputData, aliceClient, user.requester.UnblindSignature(aliceClient.BlindedOutputSignatures.First())));
+ users.Add((user.requester, user.blinded, user.activeOutputAddress, user.changeOutputAddress, user.inputProofModels, user.userInputData, aliceClient, null));
}
Assert.Equal(users.Count, roundConfig.AnonymitySet);
- var confirmationRequests = new List<Task<CcjRoundPhase>>();
+ var confirmationRequests = new List<Task<(CcjRoundPhase currentPhase, IEnumerable<(BitcoinAddress output, BlindSignature signature, int level)>)>>();
foreach (var user in users)
{
@@ -2528,16 +2510,21 @@ namespace WalletWasabi.Tests
}
CcjRoundPhase roundPhase = CcjRoundPhase.InputRegistration;
+ int k = 0;
foreach (var request in confirmationRequests)
{
+ var resp = await request;
if (roundPhase == CcjRoundPhase.InputRegistration)
{
- roundPhase = await request;
+ roundPhase = resp.currentPhase;
}
else
{
- Assert.Equal(roundPhase, await request);
+ Assert.Equal(roundPhase, resp.currentPhase);
}
+
+ var user = users.ElementAt(k);
+ user.unblindedSignature = resp.Item2.First().signature;
}
using (var satoshiClient = new SatoshiClient(baseUri))
@@ -2580,12 +2567,12 @@ namespace WalletWasabi.Tests
Assert.Equal(roundId, aliceClient.RoundId);
}
// Because it's valuetuple.
- users.Add((user.requester, user.blinded, user.activeOutputAddress, user.changeOutputAddress, user.inputProofModels, user.userInputData, aliceClient, user.requester.UnblindSignature(aliceClient.BlindedOutputSignatures.First())));
+ users.Add((user.requester, user.blinded, user.activeOutputAddress, user.changeOutputAddress, user.inputProofModels, user.userInputData, aliceClient, null));
}
Assert.Equal(users.Count, roundConfig.AnonymitySet);
- confirmationRequests = new List<Task<CcjRoundPhase>>();
+ confirmationRequests = new List<Task<(CcjRoundPhase currentPhase, IEnumerable<(BitcoinAddress output, BlindSignature signature, int level)>)>>();
foreach (var user in users)
{
@@ -2593,16 +2580,21 @@ namespace WalletWasabi.Tests
}
roundPhase = CcjRoundPhase.InputRegistration;
+ k = 0;
foreach (var request in confirmationRequests)
{
+ var resp = await request;
if (roundPhase == CcjRoundPhase.InputRegistration)
{
- roundPhase = await request;
+ roundPhase = resp.currentPhase;
}
else
{
- Assert.Equal(roundPhase, await request);
+ Assert.Equal(roundPhase, resp.currentPhase);
}
+
+ var user = users.ElementAt(k);
+ user.unblindedSignature = resp.Item2.First().signature;
}
using (var satoshiClient = new SatoshiClient(baseUri))
@@ -2723,14 +2715,13 @@ namespace WalletWasabi.Tests
Assert.Equal(roundId, aliceClient.RoundId);
}
// Because it's valuetuple.
- users.Add((user.requester, user.blinded, user.activeOutputAddress, user.changeOutputAddress, user.inputProofModels, user.userInputData, aliceClient, user.requester.UnblindSignature(aliceClient.BlindedOutputSignatures.First())));
+ users.Add((user.requester, user.blinded, user.activeOutputAddress, user.changeOutputAddress, user.inputProofModels, user.userInputData, aliceClient, null));
}
Logger.TurnOn();
Assert.Equal(users.Count, roundConfig.AnonymitySet);
-
- var confirmationRequests = new List<Task<CcjRoundPhase>>();
+ var confirmationRequests = new List<Task<(CcjRoundPhase currentPhase, IEnumerable<(BitcoinAddress output, BlindSignature signature, int level)>)>>();
foreach (var user in users)
{
@@ -2738,16 +2729,21 @@ namespace WalletWasabi.Tests
}
CcjRoundPhase roundPhase = CcjRoundPhase.InputRegistration;
+ int k = 0;
foreach (var request in confirmationRequests)
{
+ var resp = await request;
if (roundPhase == CcjRoundPhase.InputRegistration)
{
- roundPhase = await request;
+ roundPhase = resp.currentPhase;
}
else
{
- Assert.Equal(roundPhase, await request);
+ Assert.Equal(roundPhase, resp.currentPhase);
}
+
+ var user = users.ElementAt(k);
+ user.unblindedSignature = resp.Item2.First().signature;
}
var outputRequests = new List<(BobClient, Task)>();

MODIFY
WalletWasabi/Backend/Models/Responses/InputsResponse.cs
WalletWasabi/Backend/Models/Responses/InputsResponse.cs
@@ -9,10 +9,6 @@ namespace WalletWasabi.Backend.Models.Responses
{
public class InputsResponse
{
- [Required, MinLength(1)]
- [JsonProperty(ItemConverterType = typeof(BigIntegerJsonConverter))]
- public IEnumerable<BigInteger> BlindedOutputSignatures { get; set; }
-
[JsonConverter(typeof(GuidJsonConverter))]
public Guid UniqueId { get; set; }

MODIFY
WalletWasabi/Models/ChaumianCoinJoin/CcjRound.cs
WalletWasabi/Models/ChaumianCoinJoin/CcjRound.cs
@@ -739,6 +739,20 @@ namespace WalletWasabi.Models.ChaumianCoinJoin
}
}
+ public int CountUsedMixingLevels()
+ {
+ using (RoundSynchronizerLock.Lock())
+ {
+ int i;
+ for (i = 1; i < MixingLevels.Count() + 1; i++)
+ {
+ if (Alices.Count(x => x.BlindedOutputSignatures.Count() > i) <= 1) break;
+ }
+
+ return i;
+ }
+ }
+
public int CountBlindSignatures()
{
using (RoundSynchronizerLock.Lock())
@@ -978,24 +992,6 @@ namespace WalletWasabi.Models.ChaumianCoinJoin
Logger.LogInfo<CcjRound>($"Round ({RoundId}): Bob ({bob.Level.Denomination}) added.");
}
- public int RemoveAlicesBy(AliceState state)
- {
- int numberOfRemovedAlices = 0;
- using (RoundSynchronizerLock.Lock())
- {
- if ((Phase != CcjRoundPhase.InputRegistration && Phase != CcjRoundPhase.ConnectionConfirmation) || Status != CcjRoundStatus.Running)
- {
- throw new InvalidOperationException("Removing Alice is only allowed in InputRegistration and ConnectionConfirmation phases.");
- }
- numberOfRemovedAlices = Alices.RemoveAll(x => x.State == state);
- }
- if (numberOfRemovedAlices != 0)
- {
- Logger.LogInfo<CcjRound>($"Round ({RoundId}): {numberOfRemovedAlices} alices in {state} state are removed.");
- }
- return numberOfRemovedAlices;
- }
-
public async Task<IEnumerable<Alice>> RemoveAlicesIfAnInputRefusedByMempoolAsync()
{
var alicesRemoved = new List<Alice>();

MODIFY
WalletWasabi/Models/ChaumianCoinJoin/ClientRoundRegistration.cs
WalletWasabi/Models/ChaumianCoinJoin/ClientRoundRegistration.cs
using NBitcoin.Crypto;
using System;
using System.Collections.Generic;
+using System.Linq;
using System.Text;
using WalletWasabi.Backend.Models.Responses;
using WalletWasabi.Helpers;
@@ -18,18 +19,19 @@ namespace WalletWasabi.Models.ChaumianCoinJoin
public BitcoinAddress ChangeAddress { get; }
- public IEnumerable<(BitcoinAddress address, BlindSignature signature, int mixingLevel)> ActiveOutputs { get; }
+ public IEnumerable<(BitcoinAddress address, BlindSignature signature, int mixingLevel)> ActiveOutputs { get; set; }
public IEnumerable<SmartCoin> CoinsRegistered { get; }
public AliceClient AliceClient { get; }
- public ClientRoundRegistration(AliceClient aliceClient, IEnumerable<SmartCoin> coinsRegistereds, IEnumerable<(BitcoinAddress output, BlindSignature signature, int level)> activeOutputs, BitcoinAddress changeAddress)
+ public ClientRoundRegistration(AliceClient aliceClient, IEnumerable<SmartCoin> coinsRegistereds, BitcoinAddress changeAddress)
{
AliceClient = Guard.NotNull(nameof(aliceClient), aliceClient);
CoinsRegistered = Guard.NotNullOrEmpty(nameof(coinsRegistereds), coinsRegistereds);
- ActiveOutputs = Guard.NotNullOrEmpty(nameof(activeOutputs), activeOutputs);
ChangeAddress = Guard.NotNull(nameof(changeAddress), changeAddress);
+
+ ActiveOutputs = Enumerable.Empty<(BitcoinAddress address, BlindSignature signature, int mixingLevel)>();
}
public bool IsPhaseActionsComleted(CcjRoundPhase phase) => CompletedPhase >= phase;

MODIFY
WalletWasabi/Services/CcjClient.cs
WalletWasabi/Services/CcjClient.cs
@@ -274,7 +274,11 @@ namespace WalletWasabi.Services
{
if (!ongoingRound.Registration.IsPhaseActionsComleted(CcjRoundPhase.ConnectionConfirmation)) // If we didn't already confirmed connection in connection confirmation phase confirm it.
{
- await ongoingRound.Registration.AliceClient.PostConfirmationAsync();
+ var res = await ongoingRound.Registration.AliceClient.PostConfirmationAsync();
+ if (res.activeOutputs.Any())
+ {
+ ongoingRound.Registration.ActiveOutputs = res.activeOutputs;
+ }
ongoingRound.Registration.SetPhaseCompleted(CcjRoundPhase.ConnectionConfirmation);
}
}
@@ -413,11 +417,17 @@ namespace WalletWasabi.Services
{
try
{
- CcjRoundPhase phase = await inputRegistrableRound.Registration.AliceClient.PostConfirmationAsync();
- if (phase > CcjRoundPhase.InputRegistration) // Then the phase went to connection confirmation (probably).
+ var res = await inputRegistrableRound.Registration.AliceClient.PostConfirmationAsync();
+
+ if (res.activeOutputs.Any())
+ {
+ inputRegistrableRound.Registration.ActiveOutputs = res.activeOutputs;
+ }
+
+ if (res.currentPhase > CcjRoundPhase.InputRegistration) // Then the phase went to connection confirmation (probably).
{
inputRegistrableRound.Registration.SetPhaseCompleted(CcjRoundPhase.ConnectionConfirmation);
- inputRegistrableRound.State.Phase = phase;
+ inputRegistrableRound.State.Phase = res.currentPhase;
}
}
catch (Exception ex)
@@ -554,7 +564,7 @@ namespace WalletWasabi.Services
KeyManager.ToFile();
SchnorrPubKey[] schnorrPubKeys = inputRegistrableRound.State.SchnorrPubKeys.ToArray();
- var requesters = new List<Requester>();
+ List<Requester> requesters = new List<Requester>();
var outputScriptHashes = new List<uint256>();
var blindedOutputScriptHashes = new List<uint256>();
@@ -629,22 +639,10 @@ namespace WalletWasabi.Services
return;
}
- var unblindedSignatures = new List<BlindSignature>();
- for (int i = 0; i < aliceClient.BlindedOutputSignatures.Length; i++)
- {
- BigInteger blindedSignature = aliceClient.BlindedOutputSignatures[i];
- Requester requester = requesters.ElementAt(i);
- BlindSignature unblindedSignature = requester.UnblindSignature(blindedSignature);
-
- uint256 outputScriptHash = outputScriptHashes.ElementAt(i);
- PubKey signerPubKey = schnorrPubKeys[i].SignerPubKey;
- if (!VerifySignature(outputScriptHash, unblindedSignature, signerPubKey))
- {
- throw new NotSupportedException($"Coordinator did not sign the blinded output properly for level: {i}.");
- }
-
- unblindedSignatures.Add(unblindedSignature);
- }
+ aliceClient.RegisteredAddresses = registeredAddresses.ToArray();
+ aliceClient.SchnorrPubKeys = schnorrPubKeys;
+ aliceClient.Requesters = requesters.ToArray();
+ aliceClient.OutputScriptHashes = outputScriptHashes.ToArray();
var coinsRegistered = new List<SmartCoin>();
foreach (TxoRef coinReference in registrableCoins)
@@ -655,15 +653,7 @@ namespace WalletWasabi.Services
State.RemoveCoinFromWaitingList(coin);
}
- var activeOutputs = new List<(BitcoinAddress output, BlindSignature signature, int level)>();
- for (int i = 0; i < Math.Min(unblindedSignatures.Count, registeredAddresses.Count); i++)
- {
- var sig = unblindedSignatures[i];
- var addr = registeredAddresses[i];
- var lvl = i;
- activeOutputs.Add((addr, sig, lvl));
- }
- var registration = new ClientRoundRegistration(aliceClient, coinsRegistered, activeOutputs, changeAddress);
+ var registration = new ClientRoundRegistration(aliceClient, coinsRegistered, changeAddress);
CcjClientRound roundRegistered = State.GetSingleOrDefaultRound(aliceClient.RoundId);
if (roundRegistered is null)

MODIFY
WalletWasabi/WebClients/Wasabi/ChaumianCoinJoin/AliceClient.cs
WalletWasabi/WebClients/Wasabi/ChaumianCoinJoin/AliceClient.cs
@@ -16,6 +16,8 @@ using System.Threading;
using WalletWasabi.Exceptions;
using WalletWasabi.Models.ChaumianCoinJoin;
using NBitcoin.BouncyCastle.Math;
+using NBitcoin.Crypto;
+using static NBitcoin.Crypto.ECDSABlinding;
namespace WalletWasabi.WebClients.Wasabi.ChaumianCoinJoin
{
@@ -23,9 +25,13 @@ namespace WalletWasabi.WebClients.Wasabi.ChaumianCoinJoin
{
public long RoundId { get; private set; }
public Guid UniqueId { get; private set; }
- public BigInteger[] BlindedOutputSignatures { get; private set; }
public Network Network { get; }
+ public BitcoinAddress[] RegisteredAddresses { get; set; }
+ public SchnorrPubKey[] SchnorrPubKeys { get; set; }
+ public Requester[] Requesters { get; set; }
+ public uint256[] OutputScriptHashes { get; set; }
+
/// <inheritdoc/>
private AliceClient(Network network, Uri baseUri, IPEndPoint torSocks5EndPoint = null) : base(baseUri, torSocks5EndPoint)
{
@@ -48,7 +54,6 @@ namespace WalletWasabi.WebClients.Wasabi.ChaumianCoinJoin
client.RoundId = inputsResponse.RoundId;
client.UniqueId = inputsResponse.UniqueId;
- client.BlindedOutputSignatures = inputsResponse.BlindedOutputSignatures.ToArray();
Logger.LogInfo<AliceClient>($"Round ({client.RoundId}), Alice ({client.UniqueId}): Registered {request.Inputs.Count()} inputs.");
return client;
@@ -72,7 +77,7 @@ namespace WalletWasabi.WebClients.Wasabi.ChaumianCoinJoin
return await CreateNewAsync(network, request, baseUri, torSocks5EndPoint);
}
- public async Task<CcjRoundPhase> PostConfirmationAsync()
+ public async Task<(CcjRoundPhase currentPhase, IEnumerable<(BitcoinAddress output, BlindSignature signature, int level)> activeOutputs)> PostConfirmationAsync()
{
using (HttpResponseMessage response = await TorClient.SendAsync(HttpMethod.Post, $"/api/v{Helpers.Constants.BackendMajorVersion}/btc/chaumiancoinjoin/confirmation?uniqueId={UniqueId}&roundId={RoundId}"))
{
@@ -81,9 +86,40 @@ namespace WalletWasabi.WebClients.Wasabi.ChaumianCoinJoin
await response.ThrowRequestExceptionFromContentAsync();
}
- CcjRoundPhase phase = await response.Content.ReadAsJsonAsync<CcjRoundPhase>();
- Logger.LogInfo<AliceClient>($"Round ({RoundId}), Alice ({UniqueId}): Confirmed connection. Phase: {phase}.");
- return phase;
+ ConnConfResp resp = await response.Content.ReadAsJsonAsync<ConnConfResp>();
+ Logger.LogInfo<AliceClient>($"Round ({RoundId}), Alice ({UniqueId}): Confirmed connection. Phase: {resp.CurrentPhase}.");
+
+ var activeOutputs = new List<(BitcoinAddress output, BlindSignature signature, int level)>();
+ if (resp.BlindedOutputSignatures != null && resp.BlindedOutputSignatures.Any())
+ {
+ var unblindedSignatures = new List<BlindSignature>();
+ var blindedSignatures = resp.BlindedOutputSignatures.ToArray();
+ for (int i = 0; i < blindedSignatures.Length; i++)
+ {
+ BigInteger blindedSignature = blindedSignatures[i];
+ Requester requester = Requesters[i];
+ BlindSignature unblindedSignature = requester.UnblindSignature(blindedSignature);
+
+ uint256 outputScriptHash = OutputScriptHashes[i];
+ PubKey signerPubKey = SchnorrPubKeys[i].SignerPubKey;
+ if (!VerifySignature(outputScriptHash, unblindedSignature, signerPubKey))
+ {
+ throw new NotSupportedException($"Coordinator did not sign the blinded output properly for level: {i}.");
+ }
+
+ unblindedSignatures.Add(unblindedSignature);
+ }
+
+ for (int i = 0; i < Math.Min(unblindedSignatures.Count, RegisteredAddresses.Length); i++)
+ {
+ var sig = unblindedSignatures[i];
+ var addr = RegisteredAddresses[i];
+ var lvl = i;
+ activeOutputs.Add((addr, sig, lvl));
+ }
+ }
+
+ return (resp.CurrentPhase, activeOutputs);
}
}

Give back signatures at connection confirmation (tests fail)