Test until Signing
MODIFY
HiddenWallet.ChaumianTumbler/Controllers/TumblerController.cs
HiddenWallet.ChaumianTumbler/Controllers/TumblerController.cs
@@ -14,6 +14,7 @@ using HiddenWallet.ChaumianTumbler.Clients;
using Nito.AsyncEx;
using HiddenWallet.ChaumianTumbler.Referee;
using ConcurrentCollections;
+using System.Text;
namespace HiddenWallet.ChaumianTumbler.Controllers
{
@@ -57,6 +58,9 @@ namespace HiddenWallet.ChaumianTumbler.Controllers
[HttpPost]
public async Task<IActionResult> InputsAsync([FromBody]InputsRequest request)
{
+ var roundId = Global.StateMachine.RoundId;
+ TumblerPhase phase = TumblerPhase.InputRegistration;
+
try
{
if (Global.StateMachine.Phase != TumblerPhase.InputRegistration || !Global.StateMachine.AcceptRequest)
@@ -169,6 +173,8 @@ namespace HiddenWallet.ChaumianTumbler.Controllers
{
alice.Inputs.Add(input);
}
+
+ AssertPhase(roundId, phase);
Global.StateMachine.Alices.Add(alice);
if (Global.StateMachine.Alices.Count >= Global.StateMachine.AnonymitySet)
@@ -189,6 +195,14 @@ namespace HiddenWallet.ChaumianTumbler.Controllers
}
}
+ private static void AssertPhase(int roundId, TumblerPhase phase)
+ {
+ if (Global.StateMachine.Phase != phase || Global.StateMachine.RoundId != roundId)
+ {
+ throw new InvalidOperationException("Phase timed out");
+ }
+ }
+
[Route("input-registration-status")]
[HttpGet]
public IActionResult InputRegistrationStatus()
@@ -217,6 +231,8 @@ namespace HiddenWallet.ChaumianTumbler.Controllers
[HttpPost]
public IActionResult ConnectionConfirmation([FromBody]ConnectionConfirmationRequest request)
{
+ var roundId = Global.StateMachine.RoundId;
+ TumblerPhase phase = TumblerPhase.ConnectionConfirmation;
try
{
if (Global.StateMachine.Phase != TumblerPhase.ConnectionConfirmation || !Global.StateMachine.AcceptRequest)
@@ -232,6 +248,7 @@ namespace HiddenWallet.ChaumianTumbler.Controllers
throw new InvalidOperationException("Connection is already confirmed");
}
+ AssertPhase(roundId, phase);
alice.State = AliceState.ConnectionConfirmed;
try
@@ -256,6 +273,8 @@ namespace HiddenWallet.ChaumianTumbler.Controllers
[HttpPost]
public IActionResult Output([FromBody]OutputRequest request)
{
+ var roundId = Global.StateMachine.RoundId;
+ TumblerPhase phase = TumblerPhase.OutputRegistration;
try
{
if (Global.StateMachine.Phase != TumblerPhase.OutputRegistration || !Global.StateMachine.AcceptRequest)
@@ -268,10 +287,11 @@ namespace HiddenWallet.ChaumianTumbler.Controllers
var output = new BitcoinWitPubKeyAddress(request.Output, expectedNetwork: Global.Config.Network);
- if(Global.RsaKey.PubKey.Verify(HexHelpers.GetBytes(request.Signature), HexHelpers.GetBytes(request.Output)))
+ if(Global.RsaKey.PubKey.Verify(HexHelpers.GetBytes(request.Signature), Encoding.UTF8.GetBytes(request.Output)))
{
try
{
+ AssertPhase(roundId, phase);
Global.StateMachine.Bobs.Add(new Bob { Output = output });
return new ObjectResult(new SuccessResponse());
}
@@ -298,6 +318,8 @@ namespace HiddenWallet.ChaumianTumbler.Controllers
[HttpGet]
public IActionResult CoinJoin(CoinJoinRequest request)
{
+ var roundId = Global.StateMachine.RoundId;
+ TumblerPhase phase = TumblerPhase.Signing;
try
{
if (Global.StateMachine.Phase != TumblerPhase.Signing || !Global.StateMachine.AcceptRequest)
@@ -313,6 +335,7 @@ namespace HiddenWallet.ChaumianTumbler.Controllers
throw new InvalidOperationException("CoinJoin has been already asked for");
}
+ AssertPhase(roundId, phase);
alice.State = AliceState.AskedForCoinJoin;
return new ObjectResult(new CoinJoinResponse

MODIFY
HiddenWallet.ChaumianTumbler/TumblerStateMachine.cs
HiddenWallet.ChaumianTumbler/TumblerStateMachine.cs
@@ -18,17 +18,18 @@ namespace HiddenWallet.ChaumianTumbler
{
public class TumblerStateMachine : IDisposable
{
- public TumblerPhase Phase { get; private set; } = TumblerPhase.InputRegistration;
+ public volatile TumblerPhase Phase;
+ public volatile int RoundId;
public Money Denomination { get; private set; }
public Money FeePerInputs { get; private set; }
public Money FeePerOutputs { get; private set; }
public int AnonymitySet { get; private set; } = (int)Global.Config.MinimumAnonymitySet;
public TimeSpan TimeSpentInInputRegistration { get; private set; } = TimeSpan.FromSeconds((int)Global.Config.AverageTimeToSpendInInputRegistrationInSeconds) + TimeSpan.FromSeconds(1); // took one sec longer, so the first round will use the same anonymity set
- public bool AcceptRequest { get; private set; } = false;
+ public volatile bool AcceptRequest;
public Stopwatch InputRegistrationStopwatch { get; private set; }
- public bool FallBackRound { get; set; } = false;
+ public volatile bool FallBackRound;
- public Transaction CoinJoin { get; private set; } = null;
+ public volatile Transaction CoinJoin;
public bool FullySignedCoinJoin => CoinJoin?.Inputs == null ? false : CoinJoin.Inputs.All(x => x.WitScript != default);
private SmartBitClient SmartBitClient { get; } = new SmartBitClient(Network.Main);
@@ -41,7 +42,11 @@ namespace HiddenWallet.ChaumianTumbler
public TumblerStateMachine()
{
-
+ Phase = TumblerPhase.InputRegistration;
+ RoundId = 0;
+ AcceptRequest = false;
+ FallBackRound = false;
+ CoinJoin = null;
}
public async Task BroadcastPhaseChangeAsync()
@@ -74,6 +79,8 @@ namespace HiddenWallet.ChaumianTumbler
{
case TumblerPhase.InputRegistration:
{
+ RoundId++;
+ Console.WriteLine($"New Round: {RoundId}");
Alices = new ConcurrentHashSet<Alice>();
Bobs = new ConcurrentHashSet<Bob>();
await SetDenominationAsync(cancel);

MODIFY
HiddenWallet.Daemon/Wrappers/WalletWrapper.cs
HiddenWallet.Daemon/Wrappers/WalletWrapper.cs
@@ -277,7 +277,7 @@ namespace HiddenWallet.Daemon.Wrappers
public async Task<BaseResponse> BuildTransactionAsync(string password, SafeAccount safeAccount, BitcoinAddress address, Money amount, FeeType feeType)
{
// todo: delete this code comment, it was just a test, if you see it, I forgot to delete it
- // await _walletJob.CoinJoinService.TumbleAsync(safeAccount == AliceAccount ? AliceAccount : BobAccount, safeAccount != AliceAccount ? AliceAccount : BobAccount, CancellationToken.None);
+ await _walletJob.CoinJoinService.TumbleAsync(safeAccount == AliceAccount ? AliceAccount : BobAccount, safeAccount != AliceAccount ? AliceAccount : BobAccount, CancellationToken.None);
if (password != _password) throw new InvalidOperationException("Wrong password");
var result = await _walletJob.BuildTransactionAsync(address.ScriptPubKey, amount, feeType, safeAccount, (bool)Global.Config.CanSpendUnconfirmed);

MODIFY
HiddenWallet.FullSpvWallet/ChaumianCoinJoin/CoinJoinService.cs
HiddenWallet.FullSpvWallet/ChaumianCoinJoin/CoinJoinService.cs
@@ -211,8 +211,8 @@ namespace HiddenWallet.FullSpvWallet.ChaumianCoinJoin
var feePerOutputs = Money.Parse(status.FeePerOutputs);
IEnumerable<Script> unusedOutputs = await WalletJob.GetUnusedScriptPubKeysAsync(AddressType.Pay2WitnessPublicKeyHash, To, HdPathType.NonHardened);
- Script activeOutput = unusedOutputs.RandomElement(); // TODO: this is sub-optimal, it'd be better to not which had been already registered and not reregister it
- var blindingResult = PubKey.Blind(activeOutput.ToBytes());
+ var activeOutput = unusedOutputs.RandomElement().GetDestinationAddress(WalletJob.CurrentNetwork); // TODO: this is sub-optimal, it'd be better to not which had been already registered and not reregister it
+ var blindingResult = PubKey.Blind(Encoding.UTF8.GetBytes(activeOutput.ToString()));
string blindedOutput = HexHelpers.ToString(blindingResult.BlindedData);
SigningKeys = new ConcurrentHashSet<BitcoinExtKey>();
@@ -269,12 +269,12 @@ namespace HiddenWallet.FullSpvWallet.ChaumianCoinJoin
// unblind the signature
var unblindedSignature = PubKey.UnblindSignature(HexHelpers.GetBytes(response.SignedBlindedOutput), blindingResult.BlindingFactor);
// verify the original data is signed
- if (!PubKey.Verify(unblindedSignature, activeOutput.ToBytes()))
+ if (!PubKey.Verify(unblindedSignature, Encoding.UTF8.GetBytes(activeOutput.ToString())))
{
throw new HttpRequestException("Tumbler did not sign the blinded output properly");
}
UnblindedSignature = HexHelpers.ToString(unblindedSignature);
- ActiveOutput = activeOutput.GetDestinationAddress(WalletJob.CurrentNetwork);
+ ActiveOutput = activeOutput;
}
else if (phase == TumblerPhase.ConnectionConfirmation)
{

Test until Signing