Add new (roundHash) DoS protection
MODIFY
HiddenWallet.ChaumianTumbler/Controllers/TumblerController.cs
HiddenWallet.ChaumianTumbler/Controllers/TumblerController.cs
@@ -75,9 +75,9 @@ namespace HiddenWallet.ChaumianTumbler.Controllers
if (request.Inputs == null || request.Inputs.Count() == 0) return new BadRequestResult();
// Check format (parse everyting))
- if(Global.BlindedOutputStore.BlindedOutputs.Contains(blindedOutputString))
+ if(Global.StateMachine.BlindedOutputs.Contains(blindedOutputString))
{
- throw new ArgumentException("Blinded output has already been registered previously");
+ throw new ArgumentException("Blinded output has already been registered");
}
byte[] blindedOutput = HexHelpers.GetBytes(blindedOutputString);
Network network = Global.Config.Network;
@@ -164,7 +164,7 @@ namespace HiddenWallet.ChaumianTumbler.Controllers
}
byte[] signature = Global.RsaKey.SignBlindedData(blindedOutput);
- Global.BlindedOutputStore.BlindedOutputs.Add(blindedOutputString);
+ Global.StateMachine.BlindedOutputs.Add(blindedOutputString);
Guid uniqueId = Guid.NewGuid();
@@ -262,7 +262,7 @@ namespace HiddenWallet.ChaumianTumbler.Controllers
try
{
- return new ObjectResult(new SuccessResponse());
+ return new ObjectResult(new ConnectionConfirmationResponse { RoundHash = Global.StateMachine.RoundHash});
}
finally
{
@@ -293,6 +293,12 @@ namespace HiddenWallet.ChaumianTumbler.Controllers
if (string.IsNullOrWhiteSpace(request.Output)) return new BadRequestResult();
if (string.IsNullOrWhiteSpace(request.Signature)) return new BadRequestResult();
+ if (string.IsNullOrWhiteSpace(request.RoundHash)) return new BadRequestResult();
+
+ if(request.RoundHash != Global.StateMachine.RoundHash)
+ {
+ throw new ArgumentException("Wrong round hash provided");
+ }
var output = new BitcoinWitPubKeyAddress(request.Output, expectedNetwork: Global.Config.Network);

MODIFY
HiddenWallet.ChaumianTumbler/Global.cs
HiddenWallet.ChaumianTumbler/Global.cs
@@ -21,7 +21,6 @@ namespace HiddenWallet.ChaumianTumbler
{
_dataDir = null;
CoinJoinStorePath = Path.Combine(DataDir, "CoinJoins.json");
- BlindedOutputStorePath = Path.Combine(DataDir, "BlindedOutputs.json");
UtxoRefereePath = Path.Combine(DataDir, "BannedUtxos.json");
string configFilePath = Path.Combine(DataDir, "Config.json");
@@ -58,15 +57,6 @@ namespace HiddenWallet.ChaumianTumbler
CoinJoinStore = new CoinJoinStore();
}
- if (File.Exists(BlindedOutputStorePath))
- {
- BlindedOutputStore = await BlindedOutputStore.CreateFromFileAsync(BlindedOutputStorePath);
- }
- else
- {
- BlindedOutputStore = new BlindedOutputStore();
- }
-
if (File.Exists(UtxoRefereePath))
{
UtxoReferee = await UtxoReferee.CreateFromFileAsync(UtxoRefereePath);
@@ -128,9 +118,6 @@ namespace HiddenWallet.ChaumianTumbler
public static CoinJoinStore CoinJoinStore;
public static string CoinJoinStorePath;
- public static BlindedOutputStore BlindedOutputStore;
- public static string BlindedOutputStorePath;
-
public static UtxoReferee UtxoReferee;
public static string UtxoRefereePath;
public static Task UtxoRefereeJob;

MODIFY
HiddenWallet.ChaumianTumbler/TumblerStateMachine.cs
HiddenWallet.ChaumianTumbler/TumblerStateMachine.cs
@@ -11,6 +11,7 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
+using System.Text;
using System.Threading;
using System.Threading.Tasks;
@@ -20,6 +21,8 @@ namespace HiddenWallet.ChaumianTumbler
{
public volatile TumblerPhase Phase;
public volatile int RoundId;
+ public volatile string RoundHash;
+
public Money Denomination { get; private set; }
public Money FeePerInputs { get; private set; }
public Money FeePerOutputs { get; private set; }
@@ -36,6 +39,7 @@ namespace HiddenWallet.ChaumianTumbler
private SmartBitClient SmartBitClient { get; } = new SmartBitClient(Network.Main);
public ConcurrentHashSet<Alice> Alices { get; private set; }
public ConcurrentHashSet<Bob> Bobs { get; private set; }
+ public ConcurrentHashSet<string> BlindedOutputs { get; private set; }
private NotificationBroadcaster _broadcaster = NotificationBroadcaster.Instance;
@@ -91,6 +95,7 @@ namespace HiddenWallet.ChaumianTumbler
RoundId++;
Console.WriteLine($"New Round: {RoundId}");
Alices = new ConcurrentHashSet<Alice>();
+ BlindedOutputs = new ConcurrentHashSet<string>();
await Global.StateMachine.BroadcastPeerRegisteredAsync();
Bobs = new ConcurrentHashSet<Bob>();
await SetDenominationAsync(cancel);
@@ -109,13 +114,14 @@ namespace HiddenWallet.ChaumianTumbler
{
TimeSpentInInputRegistration = InputRegistrationStopwatch.Elapsed;
}
- await Global.BlindedOutputStore.ToFileAsync(Global.BlindedOutputStorePath);
UpdatePhase(TumblerPhase.ConnectionConfirmation);
break;
}
case TumblerPhase.ConnectionConfirmation:
{
+ RoundHash = NBitcoinHelpers.HashOutpoints(Alices.SelectMany(x => x.Inputs).Select(x => x.OutPoint));
+
await BroadcastPhaseChangeAsync();
using (var cts = CancellationTokenSource.CreateLinkedTokenSource(cancel, _ctsPhaseCancel.Token))
{

MODIFY
HiddenWallet.FullSpvWallet/ChaumianCoinJoin/CoinJoinService.cs
HiddenWallet.FullSpvWallet/ChaumianCoinJoin/CoinJoinService.cs
@@ -4,6 +4,7 @@ using HiddenWallet.ChaumianCoinJoin;
using HiddenWallet.ChaumianCoinJoin.Models;
using HiddenWallet.Crypto;
using HiddenWallet.FullSpv;
+using HiddenWallet.Helpers;
using HiddenWallet.KeyManagement;
using HiddenWallet.Models;
using Microsoft.AspNetCore.SignalR.Client;
@@ -50,6 +51,7 @@ namespace HiddenWallet.FullSpvWallet.ChaumianCoinJoin
public ConcurrentHashSet<BitcoinExtKey> SigningKeys { get; private set; }
public ConcurrentHashSet<Coin> Inputs { get; private set; }
public string UnblindedSignature { get; private set; }
+ public string RoundHash { get; private set; }
public CoinJoinService(WalletJob walletJob)
{
@@ -310,7 +312,8 @@ namespace HiddenWallet.FullSpvWallet.ChaumianCoinJoin
{
UniqueId = UniqueAliceId
};
- await TumblerClient.PostConnectionConfirmationAsync(request, cancel);
+ var resp = await TumblerClient.PostConnectionConfirmationAsync(request, cancel);
+ RoundHash = resp.RoundHash;
}
else if (phase == TumblerPhase.OutputRegistration)
{
@@ -321,7 +324,8 @@ namespace HiddenWallet.FullSpvWallet.ChaumianCoinJoin
var request = new OutputRequest
{
Output = ActiveOutput.ToString(),
- Signature = UnblindedSignature
+ Signature = UnblindedSignature,
+ RoundHash = RoundHash
};
await TumblerClient.PostOutputAsync(request, cancel);
}
@@ -336,6 +340,7 @@ namespace HiddenWallet.FullSpvWallet.ChaumianCoinJoin
UniqueId = UniqueAliceId
};
CoinJoin = new Transaction((await TumblerClient.PostCoinJoinAsync(request, cancel)).Transaction);
+
if (!(CoinJoin.Outputs.Any(x => x.ScriptPubKey == ActiveOutput.ScriptPubKey && x.Value >= Denomination)))
{
throw new InvalidOperationException("Tumbler did not add enough value to the active output");
@@ -344,6 +349,10 @@ namespace HiddenWallet.FullSpvWallet.ChaumianCoinJoin
{
throw new InvalidOperationException("Tumbler did not add enough value to the change output");
}
+ if (RoundHash != NBitcoinHelpers.HashOutpoints(CoinJoin.Inputs.Select(x=>x.PrevOut)))
+ {
+ throw new InvalidOperationException("Tumbler provided invalid roundHash");
+ }
new TransactionBuilder()
.AddKeys(SigningKeys.ToArray())

MODIFY
HiddenWallet/ChaumianCoinJoin/ChaumianTumblerClient.cs
HiddenWallet/ChaumianCoinJoin/ChaumianTumblerClient.cs
@@ -82,7 +82,7 @@ namespace HiddenWallet.ChaumianCoinJoin
}
}
- public async Task PostConnectionConfirmationAsync(ConnectionConfirmationRequest request, CancellationToken cancel)
+ public async Task<ConnectionConfirmationResponse> PostConnectionConfirmationAsync(ConnectionConfirmationRequest request, CancellationToken cancel)
{
using (await _asyncLock.LockAsync())
{
@@ -98,6 +98,8 @@ namespace HiddenWallet.ChaumianCoinJoin
if (!response.IsSuccessStatusCode) throw new HttpRequestException(response.StatusCode.ToString());
string responseString = await response.Content.ReadAsStringAsync();
AssertSuccess(responseString);
+
+ return JsonConvert.DeserializeObject<ConnectionConfirmationResponse>(responseString);
}
}

MODIFY
HiddenWallet/ChaumianCoinJoin/Models/OutputRequest.cs
HiddenWallet/ChaumianCoinJoin/Models/OutputRequest.cs
@@ -9,5 +9,6 @@ namespace HiddenWallet.ChaumianCoinJoin.Models
{
public string Output { get; set; }
public string Signature { get; set; }
+ public string RoundHash { get; set; }
}
}

Add new (roundHash) DoS protection