Add default blame script
ADD
WalletWasabi/JsonConverters/Bitcoin/DefaultValueScriptAttribute.cs
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace WalletWasabi.JsonConverters.Bitcoin
+{
+ public class DefaultValueScriptAttribute : DefaultValueAttribute
+ {
+ public DefaultValueScriptAttribute(string json) : base(ScriptJsonConverter.Parse(json))
+ {
+ }
+ }
+}

ADD
WalletWasabi/JsonConverters/Bitcoin/ScriptJsonConverter.cs
+using System;
+using System.ComponentModel;
+using System.Text;
+using NBitcoin;
+using Newtonsoft.Json;
+
+namespace WalletWasabi.JsonConverters.Bitcoin
+{
+ public class ScriptJsonConverter : JsonConverter
+ {
+ /// <inheritdoc />
+ public override bool CanConvert(Type objectType)
+ {
+ return objectType == typeof(Script);
+ }
+
+ /// <inheritdoc />
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ var stringValue = reader.Value as string;
+ return Parse(stringValue);
+ }
+
+ public static Script Parse(string? stringValue)
+ {
+ return new Script(stringValue);
+ }
+
+ /// <inheritdoc />
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ var v = (Script)value;
+ writer.WriteValue(v.ToString());
+ }
+ }
+}

MODIFY
WalletWasabi/WabiSabi/Backend/Rounds/Arena.cs
WalletWasabi/WabiSabi/Backend/Rounds/Arena.cs
@@ -109,7 +109,8 @@ namespace WalletWasabi.WabiSabi.Backend.Rounds
{
long aliceSum = round.Alices.Sum(x => x.CalculateRemainingAmountCredentials(round.FeeRate));
long bobSum = round.Bobs.Sum(x => x.CredentialAmount);
- if (aliceSum == bobSum || round.OutputRegistrationStart + Config.OutputRegistrationTimeout < DateTimeOffset.UtcNow)
+ var diff = aliceSum - bobSum;
+ if (diff == 0 || round.OutputRegistrationStart + Config.OutputRegistrationTimeout < DateTimeOffset.UtcNow)
{
var coinjoin = round.Coinjoin;
// Add inputs:
@@ -131,19 +132,13 @@ namespace WalletWasabi.WabiSabi.Backend.Rounds
coinjoin.Inputs.SortByAmount(spentCoins);
coinjoin.Outputs.SortByAmount();
- //// 2. If there are less Bobs than Alices, then add our own address. The malicious Alice, who will refuse to sign.
- //var derivationIndex = RoundConfig.CoordinatorExtPubKeyCurrentDepth + 1;
- //for (int i = 0; i < MixingLevels.Count(); i++)
- //{
- // var aliceCountInLevel = Alices.Count(x => i < x.BlindedOutputScripts.Length);
- // var missingBobCount = aliceCountInLevel - Bobs.Count(x => x.Level == MixingLevels.GetLevel(i));
- // for (int j = 0; j < missingBobCount; j++)
- // {
- // var denomination = MixingLevels.GetLevel(i).Denomination;
- // transaction.Outputs.AddWithOptimize(denomination, RoundConfig.DeriveCoordinatorScript(derivationIndex));
- // derivationIndex++;
- // }
- //}
+ // If timeout we must fill up the outputs to build a reasonable transaction.
+ // This won't be signed by the alice who failed to provide output, so we know who to ban.
+ if (diff > round.MinRegistrableAmount)
+ {
+ var diffMoney = Money.Satoshis(diff);
+ coinjoin.Outputs.AddWithOptimize(diffMoney, Config.BlameScript);
+ }
round.SetPhase(Phase.TransactionSigning);
}

MODIFY
WalletWasabi/WabiSabi/Backend/WabiSabiConfig.cs
WalletWasabi/WabiSabi/Backend/WabiSabiConfig.cs
@@ -94,5 +94,13 @@ namespace WalletWasabi.WabiSabi.Backend
public double MinInputCountByRoundMultiplier { get; set; } = 0.5;
public uint MinInputCountByRound => (uint)(MaxInputCountByRound * MinInputCountByRoundMultiplier);
+
+ /// <summary>
+ /// If money comes to the blame script, then either an attacker lost money or there's a client bug.
+ /// </summary>
+ [DefaultValueScript("0 1251dec2e6a6694a789f0cca6c2a9cfb4c74fb4e")]
+ [JsonProperty(PropertyName = "BlameScript", DefaultValueHandling = DefaultValueHandling.Populate)]
+ [JsonConverter(typeof(ScriptJsonConverter))]
+ public Script BlameScript { get; set; } = new Script("0 1251dec2e6a6694a789f0cca6c2a9cfb4c74fb4e");
}
}

Add default blame script