Implement `connection-confirmation`
RENAME
HiddenWallet.ChaumianTumbler/Models/AliceModel.cs
HiddenWallet.ChaumianTumbler/Clients/Alice.cs
@@ -5,12 +5,13 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
-namespace HiddenWallet.ChaumianTumbler.Models
+namespace HiddenWallet.ChaumianTumbler.Clients
{
- public class AliceModel
+ public class Alice
{
public Guid UniqueId { get; set; }
- public ConcurrentHashSet<TxOut> Inputs { get; set; }
+ public ConcurrentHashSet<(TxOut Output, OutPoint OutPoint)> Inputs { get; set; }
public BitcoinWitPubKeyAddress ChangeOutput { get; set; }
+ public AliceState State { get; set; }
}
}

MODIFY
HiddenWallet.ChaumianTumbler/Controllers/TumblerController.cs
HiddenWallet.ChaumianTumbler/Controllers/TumblerController.cs
@@ -10,6 +10,8 @@ using Org.BouncyCastle.Utilities.Encoders;
using NBitcoin;
using NBitcoin.RPC;
using HiddenWallet.ChaumianTumbler.Store;
+using HiddenWallet.ChaumianTumbler.Clients;
+using Nito.AsyncEx;
namespace HiddenWallet.ChaumianTumbler.Controllers
{
@@ -67,6 +69,7 @@ namespace HiddenWallet.ChaumianTumbler.Controllers
}
}
+ private readonly AsyncLock InputRegistrationLock = new AsyncLock();
[Route("inputs")]
[HttpPost]
public async Task<IActionResult> InputsAsync(InputsRequest request)
@@ -88,11 +91,23 @@ namespace HiddenWallet.ChaumianTumbler.Controllers
Network network = Global.Config.Network;
var changeOutput = new BitcoinWitPubKeyAddress(request.ChangeOutput, expectedNetwork: network);
if (request.Inputs.Count() > Global.Config.MaximumInputsPerAlices) throw new NotSupportedException("Too many inputs provided");
- var inputs = new HashSet<TxOut>();
+ var inputs = new HashSet<(TxOut Output, OutPoint OutPoint)>();
+
+ using (await InputRegistrationLock.LockAsync())
+ {
foreach (InputProofModel input in request.Inputs)
{
var op = new OutPoint();
op.FromHex(input.Input);
+ if (inputs.Any(x => x.OutPoint.Hash == op.Hash && x.OutPoint.N == op.N))
+ {
+ throw new ArgumentException("Attempting to register an input twice is not permitted");
+ }
+ if (Global.StateMachine.Alices.SelectMany(x => x.Inputs).Any(x => x.OutPoint.Hash == op.Hash && x.OutPoint.N == op.N))
+ {
+ throw new ArgumentException("Input is already registered by another Alice");
+ }
+
var txOutResponse = await Global.RpcClient.SendCommandAsync(RPCOperations.gettxout, op.Hash.ToString(), op.N, true);
// Check if inputs are unspent
if (txOutResponse.Result == null)
@@ -122,12 +137,12 @@ namespace HiddenWallet.ChaumianTumbler.Controllers
throw new ArgumentException("Provided proof is invalid");
}
var txout = new TxOut(new Money(value, MoneyUnit.BTC), scriptPubKey);
- inputs.Add(txout);
+ inputs.Add((txout, op));
}
// Check if inputs have enough coins
Money amount = Money.Zero;
- foreach (Money val in inputs.Select(x => x.Value))
+ foreach (Money val in inputs.Select(x => x.Output.Value))
{
amount += val;
}
@@ -140,10 +155,11 @@ namespace HiddenWallet.ChaumianTumbler.Controllers
byte[] signature = Global.RsaKey.SignBlindedData(blindedOutput);
Guid uniqueId = Guid.NewGuid();
- var alice = new AliceModel
+ var alice = new Alice
{
UniqueId = uniqueId,
- ChangeOutput = changeOutput
+ ChangeOutput = changeOutput,
+ State = AliceState.InputsRegistered
};
foreach (var input in inputs)
{
@@ -162,6 +178,7 @@ namespace HiddenWallet.ChaumianTumbler.Controllers
SignedBlindedOutput = HexHelpers.ToString(signature)
});
}
+ }
catch (Exception ex)
{
return new ObjectResult(new FailureResponse { Message = ex.Message, Details = ex.ToString() });
@@ -204,8 +221,21 @@ namespace HiddenWallet.ChaumianTumbler.Controllers
}
if (request.UniqueId == null) return new BadRequestResult();
+ Alice alice = FindAlice(request.UniqueId);
- throw new NotImplementedException();
+ if (alice.State == AliceState.ConnectionConfirmed)
+ {
+ throw new InvalidOperationException("Connection is already confirmed");
+ }
+
+ alice.State = AliceState.ConnectionConfirmed;
+
+ if(Global.StateMachine.Alices.All(x=>x.State == AliceState.ConnectionConfirmed))
+ {
+ Global.StateMachine.UpdatePhase(TumblerPhase.OutputRegistration);
+ }
+
+ return new ObjectResult(new SuccessResponse());
}
catch (Exception ex)
{
@@ -236,7 +266,7 @@ namespace HiddenWallet.ChaumianTumbler.Controllers
[Route("coinjoin")]
[HttpGet]
- public IActionResult CoinJoin()
+ public IActionResult CoinJoin(CoinJoinRequest request)
{
try
{
@@ -245,6 +275,9 @@ namespace HiddenWallet.ChaumianTumbler.Controllers
return new ObjectResult(new FailureResponse { Message = "Wrong phase", Details = "" });
}
+ if (request.UniqueId == null) return new BadRequestResult();
+ Alice alice = FindAlice(request.UniqueId);
+
throw new NotImplementedException();
}
catch (Exception ex)
@@ -264,7 +297,10 @@ namespace HiddenWallet.ChaumianTumbler.Controllers
return new ObjectResult(new FailureResponse { Message = "Wrong phase", Details = "" });
}
+ if (request.UniqueId == null) return new BadRequestResult();
if (request.Signature == null) return new BadRequestResult();
+ Alice alice = FindAlice(request.UniqueId);
+
throw new NotImplementedException();
}
catch (Exception ex)
@@ -272,5 +308,16 @@ namespace HiddenWallet.ChaumianTumbler.Controllers
return new ObjectResult(new FailureResponse { Message = ex.Message, Details = ex.ToString() });
}
}
+
+ private static Alice FindAlice(string uniqueId)
+ {
+ Alice alice = Global.StateMachine.Alices.FirstOrDefault(x => x.UniqueId == new Guid(uniqueId));
+ if (alice == default(Alice))
+ {
+ throw new ArgumentException("Wrong uniqueId");
+ }
+
+ return alice;
+ }
}
}

MODIFY
HiddenWallet.ChaumianTumbler/Models/SignatureRequest.cs
HiddenWallet.ChaumianTumbler/Models/SignatureRequest.cs
@@ -7,6 +7,7 @@ namespace HiddenWallet.ChaumianTumbler.Models
{
public class SignatureRequest
{
+ public string UniqueId { get; set; }
public string Signature { get; set; }
}
}

Implement `connection-confirmation`