public async Task <uint160.MutableUint160> BeginOpenChannel( [ModelBinder(BinderType = typeof(TumblerParametersModelBinder))] ClassicTumblerParameters tumblerId, [FromBody] OpenChannelRequest request) { if (tumblerId == null) { throw new ArgumentNullException("tumblerId"); } var height = Services.BlockExplorerService.GetCurrentHeight(); if (Repository.IsUsed(request.CycleStart, request.Nonce)) { throw new ActionResultException(BadRequest("duplicate-query")); } var cycle = GetCycle(request.CycleStart); if (!cycle.IsInPhase(CyclePhase.TumblerChannelEstablishment, height)) { throw new ActionResultException(BadRequest("invalid-phase")); } var fee = await Services.FeeService.GetFeeRateAsync(); try { if (!Parameters.VoucherKey.PublicKey.Verify(request.Signature, NBitcoin.Utils.ToBytes((uint)request.CycleStart, true), request.Nonce)) { throw new ActionResultException(BadRequest("incorrect-voucher")); } if (!Repository.MarkUsedNonce(request.CycleStart, request.Nonce)) { throw new ActionResultException(BadRequest("duplicate-query")); } var escrowKey = new Key(); var escrow = new EscrowScriptPubKeyParameters(); escrow.LockTime = cycle.GetTumblerLockTime(); escrow.Receiver = request.EscrowKey; escrow.Initiator = escrowKey.PubKey; var channelId = new uint160(RandomUtils.GetBytes(20)); Logs.Tumbler.LogInformation($"Cycle {cycle.Start} Asked to open channel"); var txOut = new TxOut(Parameters.Denomination, escrow.ToScript().WitHash.ScriptPubKey.Hash); var unused = Services.WalletService.FundTransactionAsync(txOut, fee) .ContinueWith(async(Task <Transaction> task) => { try { var tx = await task.ConfigureAwait(false); var correlation = new CorrelationId(channelId); Tracker.TransactionCreated(cycle.Start, TransactionType.TumblerEscrow, tx.GetHash(), correlation); //Logging/Broadcast per funding TX one time if (Repository.MarkUsedNonce(cycle.Start, new uint160(tx.GetHash().ToBytes().Take(20).ToArray()))) { var bobCount = Parameters.CountEscrows(tx, Client.Identity.Bob); Logs.Tumbler.LogInformation($"Cycle {cycle.Start} channel created {tx.GetHash()} with {bobCount} users"); await Services.BroadcastService.BroadcastAsync(tx).ConfigureAwait(false); } await Services.BlockExplorerService.TrackAsync(txOut.ScriptPubKey).ConfigureAwait(false); Tracker.AddressCreated(cycle.Start, TransactionType.TumblerEscrow, txOut.ScriptPubKey, correlation); var coin = tx.Outputs.AsCoins().First(o => o.ScriptPubKey == txOut.ScriptPubKey && o.TxOut.Value == txOut.Value); var session = new PromiseServerSession(Parameters.CreatePromiseParamaters()); var redeem = await Services.WalletService.GenerateAddressAsync().ConfigureAwait(false); session.ConfigureEscrowedCoin(channelId, coin.ToScriptCoin(escrow.ToScript()), escrowKey, redeem.ScriptPubKey); var redeemTx = session.CreateRedeemTransaction(fee); Services.TrustedBroadcastService.Broadcast(cycle.Start, TransactionType.TumblerRedeem, correlation, redeemTx); Repository.Save(cycle.Start, session); Tracker.AddressCreated(cycle.Start, TransactionType.TumblerRedeem, redeem.ScriptPubKey, correlation); } catch (Exception ex) { Logs.Tumbler.LogCritical(new EventId(), ex, "Error during escrow transaction callback"); } }); return(channelId.AsBitcoinSerializable()); } catch (NotEnoughFundsException ex) { Logs.Tumbler.LogInformation(ex.Message); throw new ActionResultException(BadRequest("tumbler-insufficient-funds")); } }
public ScriptCoinModel OpenChannel( [ModelBinder(BinderType = typeof(TumblerParametersModelBinder))] ClassicTumblerParameters tumblerId, [FromBody] OpenChannelRequest request) { if (tumblerId == null) { throw new ArgumentNullException("tumblerId"); } var height = Services.BlockExplorerService.GetCurrentHeight(); var cycle = GetCycle(request.CycleStart); if (!cycle.IsInPhase(CyclePhase.TumblerChannelEstablishment, height)) { throw new ActionResultException(BadRequest("invalid-phase")); } var fee = Services.FeeService.GetFeeRate(); try { if (!Parameters.VoucherKey.Verify(request.Signature, NBitcoin.Utils.ToBytes((uint)request.CycleStart, true), request.Nonce)) { throw new ActionResultException(BadRequest("incorrect-voucher")); } if (!Repository.MarkUsedNonce(request.CycleStart, request.Nonce)) { throw new ActionResultException(BadRequest("nonce-already-used")); } var escrowKey = new Key(); var escrow = new EscrowScriptPubKeyParameters(); escrow.LockTime = cycle.GetTumblerLockTime(); escrow.Receiver = request.EscrowKey; escrow.Initiator = escrowKey.PubKey; Logs.Tumbler.LogInformation($"Cycle {cycle.Start} Asked to open channel"); var txOut = new TxOut(Parameters.Denomination, escrow.ToScript().Hash); var tx = Services.WalletService.FundTransaction(txOut, fee); var correlation = escrow.GetCorrelation(); var escrowTumblerLabel = $"Cycle {cycle.Start} Tumbler Escrow"; Services.BlockExplorerService.Track(txOut.ScriptPubKey); Tracker.AddressCreated(cycle.Start, TransactionType.TumblerEscrow, txOut.ScriptPubKey, correlation); Tracker.TransactionCreated(cycle.Start, TransactionType.TumblerEscrow, tx.GetHash(), correlation); Logs.Tumbler.LogInformation($"Cycle {cycle.Start} Channel created " + tx.GetHash()); var coin = tx.Outputs.AsCoins().First(o => o.ScriptPubKey == txOut.ScriptPubKey && o.TxOut.Value == txOut.Value); var session = new PromiseServerSession(Parameters.CreatePromiseParamaters()); var redeem = Services.WalletService.GenerateAddress(); session.ConfigureEscrowedCoin(coin.ToScriptCoin(escrow.ToScript()), escrowKey, redeem.ScriptPubKey); Repository.Save(cycle.Start, session); Services.BroadcastService.Broadcast(tx); var redeemTx = session.CreateRedeemTransaction(fee); Tracker.AddressCreated(cycle.Start, TransactionType.TumblerRedeem, redeem.ScriptPubKey, correlation); Services.TrustedBroadcastService.Broadcast(cycle.Start, TransactionType.TumblerRedeem, correlation, redeemTx); return(new ScriptCoinModel(session.EscrowedCoin)); } catch (PuzzleException) { throw new ActionResultException(BadRequest("incorrect-voucher")); } catch (NotEnoughFundsException ex) { Logs.Tumbler.LogInformation(ex.Message); throw new ActionResultException(BadRequest("tumbler-insufficient-funds")); } }
public async Task <PuzzleSolution> SignVoucher( [ModelBinder(BinderType = typeof(TumblerParametersModelBinder))] ClassicTumblerParameters tumblerId, [FromBody] SignVoucherRequest request) { if (tumblerId == null) { throw new ArgumentNullException("tumblerId"); } if (request.UnsignedVoucher == null) { throw new ActionResultException(BadRequest("Missing UnsignedVoucher")); } if (request.MerkleProof == null) { throw new ActionResultException(BadRequest("Missing MerkleProof")); } if (request.Transaction == null) { throw new ActionResultException(BadRequest("Missing Transaction")); } if (request.ClientEscrowKey == null) { throw new ActionResultException(BadRequest("Missing ClientEscrowKey")); } if (request.ChannelId == null) { throw new ActionResultException(BadRequest("Missing ChannelId")); } var cycle = GetCycle(request.Cycle); var height = Services.BlockExplorerService.GetCurrentHeight(); if (!cycle.IsInPhase(CyclePhase.ClientChannelEstablishment, height)) { throw new ActionResultException(BadRequest("invalid-phase")); } if (request.MerkleProof.PartialMerkleTree .GetMatchedTransactions() .FirstOrDefault() != request.Transaction.GetHash() || !request.MerkleProof.Header.CheckProofOfWork()) { Logs.Tumbler.LogDebug("Invalid transaction merkle proof"); throw new ActionResultException(BadRequest("invalid-merkleproof")); } var confirmations = Services.BlockExplorerService.GetBlockConfirmations(request.MerkleProof.Header.GetHash()); if ((confirmations < Parameters.CycleGenerator.FirstCycle.SafetyPeriodDuration)) { Logs.Tumbler.LogDebug("Not enough confirmations"); throw new ActionResultException(BadRequest("not-enough-confirmation")); } var transaction = request.Transaction; if (transaction.Outputs.Count > 2) { Logs.Tumbler.LogDebug("Incorrect number of outputs"); throw new ActionResultException(BadRequest("invalid-transaction")); } var key = Repository.GetKey(cycle.Start, request.KeyReference); if (Repository.IsUsed(cycle.Start, request.ChannelId)) { throw new ActionResultException(BadRequest("duplicate-query")); } var expectedEscrow = new EscrowScriptPubKeyParameters(request.ClientEscrowKey, key.PubKey, cycle.GetClientLockTime()); var expectedTxOut = new TxOut(Parameters.Denomination + Parameters.Fee, expectedEscrow.ToScript().WitHash.ScriptPubKey.Hash); var escrowedCoin = transaction .Outputs .AsCoins() .Where(c => c.TxOut.Value == expectedTxOut.Value && c.TxOut.ScriptPubKey == expectedTxOut.ScriptPubKey) .Select(c => c.ToScriptCoin(expectedEscrow.ToScript())) .FirstOrDefault(); if (escrowedCoin == null) { Logs.Tumbler.LogDebug("Could not find escrowed coin"); throw new ActionResultException(BadRequest("invalid-transaction")); } var solverServerSession = new SolverServerSession(Runtime.TumblerKey, Parameters.CreateSolverParamaters()); solverServerSession.SetChannelId(request.ChannelId); solverServerSession.ConfigureEscrowedCoin(escrowedCoin, key); await Services.BlockExplorerService.TrackAsync(escrowedCoin.ScriptPubKey); if (!await Services.BlockExplorerService.TrackPrunedTransactionAsync(request.Transaction, request.MerkleProof)) { throw new ActionResultException(BadRequest("invalid-merkleproof")); } //Without this one, someone could spam the nonce db by replaying this request with different channelId if (!Repository.MarkUsedNonce(cycle.Start, Hashes.Hash160(escrowedCoin.Outpoint.ToBytes()))) { throw new ActionResultException(BadRequest("duplicate-query")); } AssertNotDuplicateQuery(cycle.Start, request.ChannelId); Repository.Save(cycle.Start, solverServerSession); Logs.Tumbler.LogInformation($"Cycle {cycle.Start} Proof of Escrow signed for " + transaction.GetHash()); var correlation = GetCorrelation(solverServerSession); Tracker.AddressCreated(cycle.Start, TransactionType.ClientEscrow, escrowedCoin.ScriptPubKey, correlation); Tracker.TransactionCreated(cycle.Start, TransactionType.ClientEscrow, request.Transaction.GetHash(), correlation); var solution = request.UnsignedVoucher.WithRsaKey(Runtime.VoucherKey.PubKey).Solve(Runtime.VoucherKey); return(solution); }
public PuzzleSolution SignVoucher( [ModelBinder(BinderType = typeof(TumblerParametersModelBinder))] ClassicTumblerParameters tumblerId, [FromBody] SignVoucherRequest request) { if (tumblerId == null) { throw new ArgumentNullException("tumblerId"); } if (request.UnsignedVoucher == null) { throw new ActionResultException(BadRequest("Missing UnsignedVoucher")); } if (request.MerkleProof == null) { throw new ActionResultException(BadRequest("Missing MerkleProof")); } if (request.Transaction == null) { throw new ActionResultException(BadRequest("Missing Transaction")); } if (request.ClientEscrowKey == null) { throw new ActionResultException(BadRequest("Missing ClientEscrowKey")); } if (request.MerkleProof.PartialMerkleTree .GetMatchedTransactions() .FirstOrDefault() != request.Transaction.GetHash() || !request.MerkleProof.Header.CheckProofOfWork()) { throw new ActionResultException(BadRequest("invalid-merkleproof")); } var confirmations = Services.BlockExplorerService.GetBlockConfirmations(request.MerkleProof.Header.GetHash()); if ((confirmations < Parameters.CycleGenerator.FirstCycle.SafetyPeriodDuration)) { throw new ActionResultException(BadRequest("not-enough-confirmation")); } var transaction = request.Transaction; if (transaction.Outputs.Count > 2) { throw new ActionResultException(BadRequest("invalid-transaction")); } var cycle = GetCycle(request.Cycle); var height = Services.BlockExplorerService.GetCurrentHeight(); if (!cycle.IsInPhase(CyclePhase.ClientChannelEstablishment, height)) { throw new ActionResultException(BadRequest("invalid-phase")); } var key = Repository.GetKey(cycle.Start, request.KeyReference); var expectedEscrow = new EscrowScriptPubKeyParameters(request.ClientEscrowKey, key.PubKey, cycle.GetClientLockTime()); var expectedTxOut = new TxOut(Parameters.Denomination + Parameters.Fee, expectedEscrow.ToScript().Hash); var escrowedCoin = transaction .Outputs .AsCoins() .Where(c => c.TxOut.Value == expectedTxOut.Value && c.TxOut.ScriptPubKey == expectedTxOut.ScriptPubKey) .Select(c => c.ToScriptCoin(expectedEscrow.ToScript())) .FirstOrDefault(); if (escrowedCoin == null) { throw new ActionResultException(BadRequest("invalid-transaction")); } try { var solverServerSession = new SolverServerSession(Runtime.TumblerKey, Parameters.CreateSolverParamaters()); solverServerSession.ConfigureEscrowedCoin(escrowedCoin, key); Services.BlockExplorerService.Track(escrowedCoin.ScriptPubKey); if (!Services.BlockExplorerService.TrackPrunedTransaction(request.Transaction, request.MerkleProof)) { throw new ActionResultException(BadRequest("invalid-merkleproof")); } if (!Repository.MarkUsedNonce(cycle.Start, new uint160(key.PubKey.Hash.ToBytes()))) { throw new ActionResultException(BadRequest("invalid-transaction")); } Repository.Save(cycle.Start, solverServerSession); Logs.Tumbler.LogInformation($"Cycle {cycle.Start} Proof of Escrow signed for " + transaction.GetHash()); var correlation = GetCorrelation(solverServerSession); Tracker.AddressCreated(cycle.Start, TransactionType.ClientEscrow, escrowedCoin.ScriptPubKey, correlation); Tracker.TransactionCreated(cycle.Start, TransactionType.ClientEscrow, request.Transaction.GetHash(), correlation); var solution = request.UnsignedVoucher.WithRsaKey(Runtime.VoucherKey.PubKey).Solve(Runtime.VoucherKey); return(solution); } catch (PuzzleException) { throw new ActionResultException(BadRequest("invalid-transaction")); } }