Make sure Alice gets back exactly as much weight units as it deserves.
MODIFY
WalletWasabi.Tests/Helpers/WabiSabiFactory.cs
WalletWasabi.Tests/Helpers/WabiSabiFactory.cs
@@ -11,6 +11,7 @@ using WalletWasabi.WabiSabi.Backend;
using WalletWasabi.WabiSabi.Backend.Models;
using WalletWasabi.WabiSabi.Backend.Rounds;
using WalletWasabi.WabiSabi.Crypto;
+using WalletWasabi.WabiSabi.Crypto.CredentialRequesting;
using WalletWasabi.WabiSabi.Models;
namespace WalletWasabi.Tests.Helpers
@@ -148,6 +149,22 @@ namespace WalletWasabi.Tests.Helpers
return (amClient.Credentials.ZeroValue.Take(amIssuer.NumberOfCredentials), weClient.Credentials.ZeroValue.Take(weIssuer.NumberOfCredentials));
}
+ public static (RealCredentialsRequest amountReq, RealCredentialsRequest weightReq) CreateRealCredentialRequests(Round? round = null, Money? amount = null, long? weight = null)
+ {
+ (var amClient, var weClient, _, _) = CreateWabiSabiClientsAndIssuers(round);
+
+ var zeroPresentables = CreateZeroCredentials(round);
+ var alice = round?.Alices.FirstOrDefault();
+ var (realAmountCredentialRequest, _) = amClient.CreateRequest(
+ new[] { amount?.Satoshi ?? (alice is null ? 1000L : alice.Coins.Select(x => x.Amount.Satoshi).Sum()) },
+ zeroPresentables.amountCredentials);
+ var (realWeightCredentialRequest, _) = weClient.CreateRequest(
+ new[] { weight ?? alice?.CalculateRemainingWeightCredentials(round!.MaxRegistrableWeightByAlice) ?? 1000L },
+ zeroPresentables.weightCredentials);
+
+ return (realAmountCredentialRequest, realWeightCredentialRequest);
+ }
+
public static ConnectionConfirmationRequest CreateConnectionConfirmationRequest(Round? round = null)
{
(var amClient, var weClient, _, _) = CreateWabiSabiClientsAndIssuers(round);
@@ -158,10 +175,10 @@ namespace WalletWasabi.Tests.Helpers
var zeroPresentables = CreateZeroCredentials(round);
var alice = round?.Alices.FirstOrDefault();
var (realAmountCredentialRequest, _) = amClient.CreateRequest(
- alice is null ? new[] { 1000L } : new[] { alice.Coins.Select(x => x.Amount.Satoshi).Sum() },
+ new[] { alice is null ? 1000L : alice.Coins.Select(x => x.Amount.Satoshi).Sum() },
zeroPresentables.amountCredentials);
var (realWeightCredentialRequest, _) = weClient.CreateRequest(
- new[] { 1000L },
+ new[] { alice?.CalculateRemainingWeightCredentials(round!.MaxRegistrableWeightByAlice) ?? 1000L },
zeroPresentables.weightCredentials);
return new ConnectionConfirmationRequest(

MODIFY
WalletWasabi.Tests/UnitTests/WabiSabi/Backend/ConfirmConnectionTests.cs
WalletWasabi.Tests/UnitTests/WabiSabi/Backend/ConfirmConnectionTests.cs
@@ -113,5 +113,29 @@ namespace WalletWasabi.Tests.UnitTests.WabiSabi.Backend
var ex = Assert.Throws<WabiSabiProtocolException>(() => handler.ConfirmConnection(req));
Assert.Equal(WabiSabiProtocolErrorCode.AliceNotFound, ex.ErrorCode);
}
+
+ [Fact]
+ public async Task IncorrectRequestedWeightCredentialsAsync()
+ {
+ MockArena arena = new();
+ WabiSabiConfig cfg = new();
+ var round = WabiSabiFactory.CreateRound(cfg);
+ round.Phase = Phase.ConnectionConfirmation;
+ var alice = WabiSabiFactory.CreateAlice();
+ round.Alices.Add(alice);
+ arena.OnTryGetRound = _ => round;
+
+ var req = WabiSabiFactory.CreateConnectionConfirmationRequest(round);
+ req = new(
+ req.RoundId,
+ req.AliceId,
+ req.ZeroAmountCredentialRequests,
+ req.RealAmountCredentialRequests,
+ req.ZeroWeightCredentialRequests,
+ WabiSabiFactory.CreateRealCredentialRequests(round, null, 1234).weightReq);
+ await using PostRequestHandler handler = new(cfg, new Prison(), arena, new MockRpcClient());
+ var ex = Assert.Throws<WabiSabiProtocolException>(() => handler.ConfirmConnection(req));
+ Assert.Equal(WabiSabiProtocolErrorCode.IncorrectRequestedWeightCredentials, ex.ErrorCode);
+ }
}
}

MODIFY
WalletWasabi/Extensions/NBitcoinExtensions.cs
WalletWasabi/Extensions/NBitcoinExtensions.cs
@@ -454,5 +454,17 @@ namespace NBitcoin
var sanityFee = FeeRate.Max(new FeeRate(Money.Coins(spikeSanity)), new FeeRate(2m));
return sanityFee;
}
+
+ public static uint EstimateSpendVsize(this Script script)
+ {
+ if (script.IsScriptType(ScriptType.P2WPKH))
+ {
+ return Constants.P2wpkhInputVirtualSize;
+ }
+ else
+ {
+ throw new NotImplementedException($"Weight estimation isn't implemented for provided script type.");
+ }
+ }
}
}

MODIFY
WalletWasabi/WabiSabi/Backend/Models/Alice.cs
WalletWasabi/WabiSabi/Backend/Models/Alice.cs
@@ -19,6 +19,10 @@ namespace WalletWasabi.WabiSabi.Backend.Models
public DateTimeOffset Deadline { get; private set; } = DateTimeOffset.UtcNow;
public IEnumerable<Coin> Coins { get; }
public IDictionary<Coin, byte[]> CoinRoundSignaturePairs { get; }
+ public Money TotalInputAmount => Coins.Sum(x => x.Amount);
+ public long TotalInputWeight => Coins.Sum(x => x.ScriptPubKey.EstimateSpendVsize() * 4);
+
+ public long CalculateRemainingWeightCredentials(uint maxRegistrableWeight) => maxRegistrableWeight - TotalInputWeight;
public void SetDeadlineRelativeTo(TimeSpan connectionConfirmationTimeout)
{

MODIFY
WalletWasabi/WabiSabi/Backend/Models/WabiSabiProtocolErrorCode.cs
WalletWasabi/WabiSabi/Backend/Models/WabiSabiProtocolErrorCode.cs
@@ -23,6 +23,7 @@ namespace WalletWasabi.WabiSabi.Backend.Models
TooMuchWeight,
InputBanned,
InputNotWhitelisted,
- AliceNotFound
+ AliceNotFound,
+ IncorrectRequestedWeightCredentials
}
}

MODIFY
WalletWasabi/WabiSabi/Backend/Rounds/Round.cs
WalletWasabi/WabiSabi/Backend/Rounds/Round.cs
@@ -41,12 +41,12 @@ namespace WalletWasabi.WabiSabi.Backend.Rounds
TransactionSigningTimeout = transactionSigningTimeout;
Random = random;
- AmountCredentialIssuer = new(new CredentialIssuerSecretKey(Random), 2, random, MaxRegistrableAmountByAlice);
- WeightCredentialIssuer = new(new CredentialIssuerSecretKey(Random), 2, random, MaxRegistrableWeightByAlice);
+ AmountCredentialIssuer = new(new(Random), 2, random, MaxRegistrableAmountByAlice);
+ WeightCredentialIssuer = new(new(Random), 2, random, MaxRegistrableWeightByAlice);
AmountCredentialIssuerParameters = AmountCredentialIssuer.CredentialIssuerSecretKey.ComputeCredentialIssuerParameters();
WeightCredentialIssuerParameters = WeightCredentialIssuer.CredentialIssuerSecretKey.ComputeCredentialIssuerParameters();
- Hash = new uint256(HashHelpers.GenerateSha256Hash($"{Id}{MaxInputCountByAlice}{MinRegistrableAmountByAlice}{MaxRegistrableAmountByAlice}{MinRegistrableWeightByAlice}{MaxRegistrableWeightByAlice}{AmountCredentialIssuerParameters}{WeightCredentialIssuerParameters}"));
+ Hash = new(HashHelpers.GenerateSha256Hash($"{Id}{MaxInputCountByAlice}{MinRegistrableAmountByAlice}{MaxRegistrableAmountByAlice}{MinRegistrableWeightByAlice}{MaxRegistrableWeightByAlice}{AmountCredentialIssuerParameters}{WeightCredentialIssuerParameters}"));
}
public Round(Round blameOf)
@@ -115,7 +115,7 @@ namespace WalletWasabi.WabiSabi.Backend.Rounds
}
var inputValueSum = Money.Zero;
- var inputWeightSum = 0;
+ var inputWeightSum = 0u;
foreach (var coinRoundSignaturePair in alice.CoinRoundSignaturePairs)
{
var coin = coinRoundSignaturePair.Key;
@@ -127,15 +127,8 @@ namespace WalletWasabi.WabiSabi.Backend.Rounds
}
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.");
- }
+ inputWeightSum += coin.TxOut.ScriptPubKey.EstimateSpendVsize() * 4;
}
if (inputValueSum < MinRegistrableAmountByAlice)
@@ -188,7 +181,7 @@ namespace WalletWasabi.WabiSabi.Backend.Rounds
if (Phase == Phase.InputRegistration)
{
alice.SetDeadlineRelativeTo(ConnectionConfirmationTimeout);
- return new ConnectionConfirmationResponse(
+ return new(
amountZeroCredentialResponse,
weightZeroCredentialResponse);
}
@@ -197,7 +190,12 @@ namespace WalletWasabi.WabiSabi.Backend.Rounds
var amountRealCredentialResponse = AmountCredentialIssuer.HandleRequest(realAmountCredentialRequests);
var weightRealCredentialResponse = WeightCredentialIssuer.HandleRequest(realWeightCredentialRequests);
- return new ConnectionConfirmationResponse(
+ if (realWeightCredentialRequests.Delta != alice.CalculateRemainingWeightCredentials(MaxRegistrableWeightByAlice))
+ {
+ throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.IncorrectRequestedWeightCredentials);
+ }
+
+ return new(
amountZeroCredentialResponse,
weightZeroCredentialResponse,
amountRealCredentialResponse,

Make sure Alice gets back exactly as much weight units as it deserves.