Make sure some checks are the responsibility of the round
MODIFY
WalletWasabi.Tests/Helpers/WabiSabiFactory.cs
WalletWasabi.Tests/Helpers/WabiSabiFactory.cs
@@ -7,6 +7,7 @@ using System.Threading.Tasks;
using WalletWasabi.Crypto;
using WalletWasabi.Crypto.Randomness;
using WalletWasabi.WabiSabi.Backend;
+using WalletWasabi.WabiSabi.Backend.Models;
using WalletWasabi.WabiSabi.Backend.Rounds;
using WalletWasabi.WabiSabi.Crypto;
using WalletWasabi.WabiSabi.Models;
@@ -51,11 +52,28 @@ namespace WalletWasabi.Tests.Helpers
public static Round CreateRound(WabiSabiConfig cfg)
=> new Round(
+ Network.Main,
cfg.MaxInputCountByAlice,
cfg.MinRegistrableAmount,
cfg.MaxRegistrableAmount,
cfg.MinRegistrableWeight,
- cfg.MaxRegistrableWeight);
+ cfg.MaxRegistrableWeight,
+ new InsecureRandom());
+
+ public static Alice CreateAlice(InputRoundSignaturePair inputSigPairs) => CreateAlice(new[] { inputSigPairs });
+
+ public static Alice CreateAlice(IEnumerable<InputRoundSignaturePair>? inputSigPairs = null)
+ {
+ var pairs = inputSigPairs ?? CreateInputRoundSignaturePairs(1);
+ var myDic = new Dictionary<Coin, byte[]>();
+
+ foreach (var pair in pairs)
+ {
+ var coin = new Coin(pair.Input, new TxOut(Money.Coins(1), BitcoinFactory.CreateScript()));
+ myDic.Add(coin, pair.RoundSignature);
+ }
+ return new Alice(myDic);
+ }
public static InputsRegistrationRequest CreateInputsRegistrationRequest(Key key, Round? round)
=> CreateInputsRegistrationRequest(new[] { key }, round);

MODIFY
WalletWasabi.Tests/UnitTests/WabiSabi/Backend/RegisterInputTests.cs
WalletWasabi.Tests/UnitTests/WabiSabi/Backend/RegisterInputTests.cs
@@ -24,7 +24,17 @@ namespace WalletWasabi.Tests.UnitTests.WabiSabi.Backend
MockArena arena = new();
arena.OnTryGetRound = _ => null;
- await using PostRequestHandler handler = new(new WabiSabiConfig(), new Prison(), arena, new MockRpcClient());
+ MockRpcClient rpc = new();
+ using Key key = new();
+
+ rpc.OnGetTxOutAsync = (_, _, _) => new()
+ {
+ Confirmations = 1,
+ ScriptPubKeyType = "witness_v0_keyhash",
+ TxOut = new TxOut(Money.Coins(1), key.PubKey.GetSegwitAddress(Network.Main))
+ };
+
+ await using PostRequestHandler handler = new(new WabiSabiConfig(), new Prison(), arena, rpc);
var req = WabiSabiFactory.CreateInputsRegistrationRequest();
var ex = await Assert.ThrowsAsync<WabiSabiProtocolException>(async () => await handler.RegisterInputAsync(req));
Assert.Equal(WabiSabiProtocolErrorCode.RoundNotFound, ex.ErrorCode);
@@ -37,14 +47,23 @@ namespace WalletWasabi.Tests.UnitTests.WabiSabi.Backend
WabiSabiConfig cfg = new();
var round = WabiSabiFactory.CreateRound(cfg);
arena.OnTryGetRound = _ => round;
+ MockRpcClient rpc = new();
+ using Key key = new();
- var req = WabiSabiFactory.CreateInputsRegistrationRequest(round);
+ rpc.OnGetTxOutAsync = (_, _, _) => new()
+ {
+ Confirmations = 1,
+ ScriptPubKeyType = "witness_v0_keyhash",
+ TxOut = new TxOut(Money.Coins(1), key.PubKey.GetSegwitAddress(Network.Main))
+ };
+
+ var req = WabiSabiFactory.CreateInputsRegistrationRequest(key, round);
+ await using PostRequestHandler handler = new(cfg, new Prison(), arena, rpc);
foreach (Phase phase in Enum.GetValues(typeof(Phase)))
{
if (phase != Phase.InputRegistration)
{
round.Phase = phase;
- await using PostRequestHandler handler = new(cfg, new Prison(), arena, new MockRpcClient());
var ex = await Assert.ThrowsAsync<WabiSabiProtocolException>(async () => await handler.RegisterInputAsync(req));
Assert.Equal(WabiSabiProtocolErrorCode.WrongPhase, ex.ErrorCode);
}
@@ -74,7 +93,17 @@ namespace WalletWasabi.Tests.UnitTests.WabiSabi.Backend
var round = WabiSabiFactory.CreateRound(cfg);
arena.OnTryGetRound = _ => round;
- await using PostRequestHandler handler = new(cfg, new Prison(), arena, new MockRpcClient());
+ MockRpcClient rpc = new();
+ using Key key = new();
+
+ rpc.OnGetTxOutAsync = (_, _, _) => new()
+ {
+ Confirmations = 1,
+ ScriptPubKeyType = "witness_v0_keyhash",
+ TxOut = new TxOut(Money.Coins(1), key.PubKey.GetSegwitAddress(Network.Main))
+ };
+
+ await using PostRequestHandler handler = new(cfg, new Prison(), arena, rpc);
var req = WabiSabiFactory.CreateInputsRegistrationRequest(WabiSabiFactory.CreateInputRoundSignaturePairs(4), round);
var ex = await Assert.ThrowsAsync<WabiSabiProtocolException>(async () => await handler.RegisterInputAsync(req));
Assert.Equal(WabiSabiProtocolErrorCode.TooManyInputs, ex.ErrorCode);
@@ -140,11 +169,20 @@ namespace WalletWasabi.Tests.UnitTests.WabiSabi.Backend
MockArena arena = new();
WabiSabiConfig cfg = new();
var round = WabiSabiFactory.CreateRound(cfg);
- round.Alices.Add(new Alice(BitcoinFactory.CreateOutPoint()));
+ round.Alices.Add(WabiSabiFactory.CreateAlice());
Round blameRound = new(round);
arena.OnTryGetRound = _ => blameRound;
- await using PostRequestHandler handler = new(cfg, new Prison(), arena, new MockRpcClient());
+ MockRpcClient rpc = new();
+ using Key key = new();
+ rpc.OnGetTxOutAsync = (_, _, _) => new()
+ {
+ Confirmations = 1,
+ ScriptPubKeyType = "witness_v0_keyhash",
+ TxOut = new TxOut(Money.Coins(1), key.PubKey.GetSegwitAddress(Network.Main))
+ };
+
+ await using PostRequestHandler handler = new(cfg, new Prison(), arena, rpc);
var req = WabiSabiFactory.CreateInputsRegistrationRequest(blameRound);
var ex = await Assert.ThrowsAsync<WabiSabiProtocolException>(async () => await handler.RegisterInputAsync(req));
Assert.Equal(WabiSabiProtocolErrorCode.InputNotWhitelisted, ex.ErrorCode);
@@ -157,7 +195,7 @@ namespace WalletWasabi.Tests.UnitTests.WabiSabi.Backend
var pair = WabiSabiFactory.CreateInputRoundSignaturePair();
WabiSabiConfig cfg = new();
var round = WabiSabiFactory.CreateRound(cfg);
- round.Alices.Add(new Alice(pair.Input));
+ round.Alices.Add(WabiSabiFactory.CreateAlice(pair));
Round blameRound = new(round);
arena.OnTryGetRound = _ => blameRound;
@@ -180,7 +218,7 @@ namespace WalletWasabi.Tests.UnitTests.WabiSabi.Backend
prison.Punish(pair.Input, Punishment.Banned, Guid.NewGuid());
WabiSabiConfig cfg = new();
var round = WabiSabiFactory.CreateRound(cfg);
- round.Alices.Add(new Alice(pair.Input));
+ round.Alices.Add(WabiSabiFactory.CreateAlice(pair));
Round blameRound = new(round);
arena.OnTryGetRound = _ => blameRound;

MODIFY
WalletWasabi/Crypto/CredentialIssuerParameters.cs
WalletWasabi/Crypto/CredentialIssuerParameters.cs
@@ -13,5 +13,7 @@ namespace WalletWasabi.Crypto
public GroupElement Cw { get; }
public GroupElement I { get; }
+
+ public override string ToString() => $"{nameof(Cw)}: {Cw}, {nameof(I)}: {I}";
}
}

MODIFY
WalletWasabi/WabiSabi/Backend/Models/Alice.cs
WalletWasabi/WabiSabi/Backend/Models/Alice.cs
@@ -9,12 +9,14 @@ namespace WalletWasabi.WabiSabi.Backend.Models
{
public class Alice
{
- public Alice(OutPoint outPoint)
+ public Alice(IDictionary<Coin, byte[]> coinRoundSignaturePairs)
{
- OutPoint = outPoint;
+ Coins = coinRoundSignaturePairs.Keys;
+ CoinRoundSignaturePairs = coinRoundSignaturePairs;
}
public Guid Id { get; } = Guid.NewGuid();
- public OutPoint OutPoint { get; }
+ public IEnumerable<Coin> Coins { get; }
+ public IDictionary<Coin, byte[]> CoinRoundSignaturePairs { get; }
}
}

MODIFY
WalletWasabi/WabiSabi/Backend/PostRequests/PostRequestHandler.cs
WalletWasabi/WabiSabi/Backend/PostRequests/PostRequestHandler.cs
@@ -39,14 +39,6 @@ namespace WalletWasabi.WabiSabi.Backend.PostRequests
DisposeGuard();
using (RunningTasks.RememberWith(RunningRequests))
{
- if (!Arena.TryGetRound(request.RoundId, out var round))
- {
- throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.RoundNotFound);
- }
- if (round.Phase != Phase.InputRegistration)
- {
- throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.WrongPhase);
- }
var inputRoundSignaturePairs = request.InputRoundSignaturePairs;
var inputs = inputRoundSignaturePairs.Select(x => x.Input);
@@ -55,21 +47,12 @@ namespace WalletWasabi.WabiSabi.Backend.PostRequests
{
throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.NonUniqueInputs);
}
- if (round.MaxInputCountByAlice < inputCount)
- {
- throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.TooManyInputs);
- }
if (inputs.Any(x => Prison.TryGet(x, out var inmate) && (!Config.AllowNotedInputRegistration || inmate.Punishment != Punishment.Noted)))
{
throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.InputBanned);
}
- if (round.IsBlameRound && inputs.Any(x => !round.BlameWhitelist.Contains(x)))
- {
- throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.InputNotWhitelisted);
- }
- var inputValueSum = Money.Zero;
- var inputWeightSum = 0;
+ Dictionary<Coin, byte[]> coinRoundSignaturePairs = new();
foreach (var inputRoundSignaturePair in inputRoundSignaturePairs)
{
OutPoint input = inputRoundSignaturePair.Input;
@@ -90,43 +73,15 @@ namespace WalletWasabi.WabiSabi.Backend.PostRequests
{
throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.ScriptNotAllowed);
}
- var address = (BitcoinWitPubKeyAddress)txOutResponse.TxOut.ScriptPubKey.GetDestinationAddress(Network);
- if (!address.VerifyMessage(round.Hash, inputRoundSignaturePair.RoundSignature))
- {
- throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.WrongRoundSignature);
- }
- inputValueSum += txOutResponse.TxOut.Value;
- if (txOutResponse.TxOut.ScriptPubKey.IsScriptType(ScriptType.P2WPKH))
- {
- // Convert conservative P2WPKH size in virtual bytes to weight units.
- inputWeightSum += Constants.P2wpkhInputVirtualSize * 4;
+ coinRoundSignaturePairs.Add(new Coin(input, txOutResponse.TxOut), inputRoundSignaturePair.RoundSignature);
}
- else
- {
- throw new NotImplementedException($"{txOutResponse.ScriptPubKeyType} weight estimation isn't implemented.");
- }
- }
-
- if (inputValueSum < round.MinRegistrableAmount)
- {
- throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.NotEnoughFunds);
- }
- if (inputValueSum > round.MaxRegistrableAmount)
- {
- throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.TooMuchFunds);
- }
-
- if (inputWeightSum < round.MinRegistrableWeight)
- {
- throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.NotEnoughWeight);
- }
- if (inputWeightSum > round.MaxRegistrableWeight)
+ if (!Arena.TryGetRound(request.RoundId, out var round))
{
- throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.TooMuchWeight);
+ throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.RoundNotFound);
}
- throw new NotImplementedException();
+ return round.RegisterAlice(new Alice(coinRoundSignaturePairs));
}
}

MODIFY
WalletWasabi/WabiSabi/Backend/Rounds/Round.cs
WalletWasabi/WabiSabi/Backend/Rounds/Round.cs
@@ -6,57 +6,148 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WalletWasabi.Crypto;
+using WalletWasabi.Crypto.Randomness;
+using WalletWasabi.Helpers;
using WalletWasabi.WabiSabi.Backend.Models;
+using WalletWasabi.WabiSabi.Crypto;
+using WalletWasabi.WabiSabi.Models;
namespace WalletWasabi.WabiSabi.Backend.Rounds
{
public class Round
{
public Round(
+ Network network,
uint maxInputCountByAlice,
Money minRegistrableAmount,
Money maxRegistrableAmount,
uint minRegistrableWeight,
- uint maxRegistrableWeight)
+ uint maxRegistrableWeight,
+ WasabiRandom random)
{
+ Network = network;
MaxInputCountByAlice = maxInputCountByAlice;
MinRegistrableAmount = minRegistrableAmount;
MaxRegistrableAmount = maxRegistrableAmount;
MinRegistrableWeight = minRegistrableWeight;
MaxRegistrableWeight = maxRegistrableWeight;
- Hash = new uint256(HashHelpers.GenerateSha256Hash($"{Id}{MaxInputCountByAlice}{MinRegistrableAmount}{MaxRegistrableAmount}{MinRegistrableWeight}{MaxRegistrableWeight}"));
+ Random = random;
+ AmountCredentialIssuer = new(new CredentialIssuerSecretKey(Random), 2, random, MaxRegistrableAmount);
+ WeightCredentialIssuer = new(new CredentialIssuerSecretKey(Random), 2, random, MaxRegistrableWeight);
+ AmountCredentialIssuerParameters = AmountCredentialIssuer.CredentialIssuerSecretKey.ComputeCredentialIssuerParameters();
+ WeightCredentialIssuerParameters = WeightCredentialIssuer.CredentialIssuerSecretKey.ComputeCredentialIssuerParameters();
+
+ Hash = new uint256(HashHelpers.GenerateSha256Hash($"{Id}{MaxInputCountByAlice}{MinRegistrableAmount}{MaxRegistrableAmount}{MinRegistrableWeight}{MaxRegistrableWeight}{AmountCredentialIssuerParameters}{WeightCredentialIssuerParameters}"));
}
public Round(Round blameOf)
: this(
+ blameOf.Network,
blameOf.MaxInputCountByAlice,
blameOf.MinRegistrableAmount,
blameOf.MaxRegistrableAmount,
blameOf.MinRegistrableWeight,
- blameOf.MaxRegistrableWeight)
+ blameOf.MaxRegistrableWeight,
+ blameOf.Random)
{
BlameOf = blameOf;
- BlameWhitelist = blameOf.Alices.Select(x => x.OutPoint).ToHashSet();
+ BlameWhitelist = blameOf
+ .Alices
+ .SelectMany(x => x.Coins)
+ .Select(x => x.Outpoint)
+ .ToHashSet();
}
- public Guid Id { get; } = Guid.NewGuid();
- public Phase Phase { get; set; } = Phase.InputRegistration;
public uint256 Hash { get; }
+ public Network Network { get; }
public uint MaxInputCountByAlice { get; }
public Money MinRegistrableAmount { get; }
public Money MaxRegistrableAmount { get; }
public uint MinRegistrableWeight { get; }
public uint MaxRegistrableWeight { get; }
+ public WasabiRandom Random { get; }
+ public CredentialIssuer AmountCredentialIssuer { get; }
+ public CredentialIssuer WeightCredentialIssuer { get; }
+ public CredentialIssuerParameters AmountCredentialIssuerParameters { get; }
+ public CredentialIssuerParameters WeightCredentialIssuerParameters { get; }
+ public Guid Id { get; } = Guid.NewGuid();
+ public Phase Phase { get; set; } = Phase.InputRegistration;
public List<Alice> Alices { get; } = new();
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 bool TryGetAlice(Guid aliceId, [NotNullWhen(true)] out Alice? alice)
{
alice = Alices.FirstOrDefault(x => x.Id == aliceId);
return alice is not null;
}
+
+ public InputsRegistrationResponse RegisterAlice(Alice alice)
+ {
+ var coins = alice.Coins;
+ if (MaxInputCountByAlice < coins.Count())
+ {
+ throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.TooManyInputs);
+ }
+ if (IsBlameRound && coins.Select(x => x.Outpoint).Any(x => !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 address = (BitcoinWitPubKeyAddress)coin.TxOut.ScriptPubKey.GetDestinationAddress(Network);
+ if (!address.VerifyMessage(Hash, signature))
+ {
+ throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.WrongRoundSignature);
+ }
+ inputValueSum += coin.TxOut.Value;
+
+ if (coin.TxOut.ScriptPubKey.IsScriptType(ScriptType.P2WPKH))
+ {
+ // Convert conservative P2WPKH size in virtual bytes to weight units.
+ inputWeightSum += Constants.P2wpkhInputVirtualSize * 4;
+ }
+ else
+ {
+ throw new NotImplementedException($"Wight estimation isn't implemented for provided script type.");
+ }
+ }
+
+ if (inputValueSum < MinRegistrableAmount)
+ {
+ throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.NotEnoughFunds);
+ }
+ if (inputValueSum > MaxRegistrableAmount)
+ {
+ throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.TooMuchFunds);
+ }
+
+ if (inputWeightSum < MinRegistrableWeight)
+ {
+ throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.NotEnoughWeight);
+ }
+ if (inputWeightSum > MaxRegistrableWeight)
+ {
+ throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.TooMuchWeight);
+ }
+
+ lock (Lock)
+ {
+ if (Phase != Phase.InputRegistration)
+ {
+ throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.WrongPhase);
+ }
+
+ throw new NotImplementedException();
+ }
+ }
}
}

MODIFY
WalletWasabi/WabiSabi/Crypto/CredentialIssuer.cs
WalletWasabi/WabiSabi/Crypto/CredentialIssuer.cs
@@ -76,7 +76,7 @@ namespace WalletWasabi.WabiSabi.Crypto
private WasabiRandom RandomNumberGenerator { get; }
- private CredentialIssuerSecretKey CredentialIssuerSecretKey { get; }
+ public CredentialIssuerSecretKey CredentialIssuerSecretKey { get; }
private CredentialIssuerParameters CredentialIssuerParameters { get; }

Make sure some checks are the responsibility of the round