Implement `coinjoin` GET request
MODIFY
HiddenWallet.ChaumianTumbler/Clients/Alice.cs
HiddenWallet.ChaumianTumbler/Clients/Alice.cs
@@ -12,6 +12,7 @@ namespace HiddenWallet.ChaumianTumbler.Clients
public Guid UniqueId { get; set; }
public ConcurrentHashSet<(TxOut Output, OutPoint OutPoint)> Inputs { get; set; }
public BitcoinWitPubKeyAddress ChangeOutput { get; set; }
+ public Money ChangeAmount { get; set; }
public AliceState State { get; set; }
}
}

MODIFY
HiddenWallet.ChaumianTumbler/Clients/AliceState.cs
HiddenWallet.ChaumianTumbler/Clients/AliceState.cs
@@ -8,6 +8,7 @@ namespace HiddenWallet.ChaumianTumbler.Clients
public enum AliceState
{
InputsRegistered,
- ConnectionConfirmed
+ ConnectionConfirmed,
+ AskedForCoinJoin
}
}

MODIFY
HiddenWallet.ChaumianTumbler/Controllers/TumblerController.cs
HiddenWallet.ChaumianTumbler/Controllers/TumblerController.cs
@@ -147,7 +147,8 @@ namespace HiddenWallet.ChaumianTumbler.Controllers
amount += val;
}
Money feeToPay = (inputs.Count() * Global.StateMachine.FeePerInputs + 2 * Global.StateMachine.FeePerOutputs);
- if (amount < Global.StateMachine.Denomination + feeToPay)
+ Money changeAmount = amount - (Global.StateMachine.Denomination + feeToPay);
+ if (changeAmount < Money.Zero)
{
throw new ArgumentException("Total provided inputs must be > denomination + fee");
}
@@ -159,6 +160,7 @@ namespace HiddenWallet.ChaumianTumbler.Controllers
{
UniqueId = uniqueId,
ChangeOutput = changeOutput,
+ ChangeAmount = changeAmount,
State = AliceState.InputsRegistered
};
foreach (var input in inputs)
@@ -304,7 +306,17 @@ namespace HiddenWallet.ChaumianTumbler.Controllers
if (request.UniqueId == null) return new BadRequestResult();
Alice alice = FindAlice(request.UniqueId);
- throw new NotImplementedException();
+ if (alice.State == AliceState.AskedForCoinJoin)
+ {
+ throw new InvalidOperationException("CoinJoin has been already asked for");
+ }
+
+ alice.State = AliceState.AskedForCoinJoin;
+
+ return new ObjectResult(new CoinJoinResponse
+ {
+ Transaction = Global.StateMachine.CoinJoin.ToHex()
+ });
}
catch (Exception ex)
{

MODIFY
HiddenWallet.ChaumianTumbler/Models/CoinJoinResponse.cs
HiddenWallet.ChaumianTumbler/Models/CoinJoinResponse.cs
@@ -9,6 +9,6 @@ namespace HiddenWallet.ChaumianTumbler.Models
public class CoinJoinResponse : BaseResponse
{
public CoinJoinResponse() => Success = true;
- public string Transactions { get; set; }
+ public string Transaction { get; set; }
}
}

MODIFY
HiddenWallet.ChaumianTumbler/TumblerStateMachine.cs
HiddenWallet.ChaumianTumbler/TumblerStateMachine.cs
@@ -26,11 +26,13 @@ namespace HiddenWallet.ChaumianTumbler
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 Stopwatch InputRegistrationStopwatch { get; private set; }
- public bool FallBackRound = false;
+ public bool FallBackRound { get; private set; } = false;
- private SmartBitClient SmartBitClient { get; }
- public ConcurrentHashSet<Alice> Alices { get; internal set; }
- public ConcurrentHashSet<Bob> Bobs { get; internal set; }
+ public Transaction CoinJoin { get; private set; } = null;
+
+ private SmartBitClient SmartBitClient { get; } = new SmartBitClient(Network.Main);
+ public ConcurrentHashSet<Alice> Alices { get; private set; }
+ public ConcurrentHashSet<Bob> Bobs { get; private set; }
private TumblerPhaseBroadcaster _broadcaster = TumblerPhaseBroadcaster.Instance;
@@ -38,7 +40,7 @@ namespace HiddenWallet.ChaumianTumbler
public TumblerStateMachine()
{
- SmartBitClient = new SmartBitClient(Network.Main);
+
}
public void BroadcastPhaseChange()
@@ -127,6 +129,8 @@ namespace HiddenWallet.ChaumianTumbler
}
case TumblerPhase.Signing:
{
+ BuildCoinJoin();
+
BroadcastPhaseChange();
using (var cts = CancellationTokenSource.CreateLinkedTokenSource(cancel, _ctsPhaseCancel.Token))
{
@@ -134,6 +138,7 @@ namespace HiddenWallet.ChaumianTumbler
}
FallBackRound = false;
UpdatePhase(TumblerPhase.InputRegistration);
+ CoinJoin = null;
break;
}
default:
@@ -149,6 +154,29 @@ namespace HiddenWallet.ChaumianTumbler
}
}
+ private void BuildCoinJoin()
+ {
+ var transaction = new Transaction();
+ foreach (var bob in Bobs)
+ {
+ transaction.AddOutput(Denomination, bob.Output);
+ }
+ foreach (var alice in Alices)
+ {
+ foreach (var input in alice.Inputs)
+ {
+ transaction.AddInput(new TxIn(input.OutPoint));
+ }
+ transaction.AddOutput(alice.ChangeAmount, alice.ChangeOutput);
+ }
+
+ var builder = new TransactionBuilder();
+ CoinJoin = builder
+ .ContinueToBuild(transaction)
+ .Shuffle()
+ .BuildTransaction(false);
+ }
+
private void CalculateAnonymitySet()
{
var min = (int)Global.Config.MinimumAnonymitySet;

Implement `coinjoin` GET request